mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Code style - Use file scoped namespaces
Code style - Ran code cleanup
This commit is contained in:
parent
e68e16df4d
commit
f6090dc296
@ -8,149 +8,148 @@ using Artemis.Core.Services.Core;
|
||||
using Artemis.Core.SkiaSharp;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A few useful constant values
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// A few useful constant values
|
||||
/// The Artemis.Core assembly
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
public static readonly Assembly CoreAssembly = typeof(Constants).Assembly;
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis application folder
|
||||
/// </summary>
|
||||
public static readonly string ApplicationFolder = Path.GetDirectoryName(typeof(Constants).Assembly.Location)!;
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis executable
|
||||
/// </summary>
|
||||
public static readonly string ExecutablePath = Utilities.GetCurrentLocation();
|
||||
|
||||
/// <summary>
|
||||
/// The base path for Artemis application data folder
|
||||
/// </summary>
|
||||
public static readonly string BaseFolder = Environment.GetFolderPath(OperatingSystem.IsWindows()
|
||||
? Environment.SpecialFolder.CommonApplicationData
|
||||
: Environment.SpecialFolder.LocalApplicationData);
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis data folder
|
||||
/// </summary>
|
||||
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis plugins folder
|
||||
/// </summary>
|
||||
public static readonly string PluginsFolder = Path.Combine(DataFolder, "Plugins");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis user layouts folder
|
||||
/// </summary>
|
||||
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
|
||||
|
||||
/// <summary>
|
||||
/// The current API version for plugins
|
||||
/// </summary>
|
||||
public static readonly Version PluginApi = new(1, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The plugin info used by core components of Artemis
|
||||
/// </summary>
|
||||
public static readonly PluginInfo CorePluginInfo = new()
|
||||
{
|
||||
/// <summary>
|
||||
/// The Artemis.Core assembly
|
||||
/// </summary>
|
||||
public static readonly Assembly CoreAssembly = typeof(Constants).Assembly;
|
||||
Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core", Version = new Version(2, 0)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis application folder
|
||||
/// </summary>
|
||||
public static readonly string ApplicationFolder = Path.GetDirectoryName(typeof(Constants).Assembly.Location)!;
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis executable
|
||||
/// </summary>
|
||||
public static readonly string ExecutablePath = Utilities.GetCurrentLocation();
|
||||
|
||||
/// <summary>
|
||||
/// The base path for Artemis application data folder
|
||||
/// </summary>
|
||||
public static readonly string BaseFolder = Environment.GetFolderPath(OperatingSystem.IsWindows()
|
||||
? Environment.SpecialFolder.CommonApplicationData
|
||||
: Environment.SpecialFolder.LocalApplicationData);
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis data folder
|
||||
/// </summary>
|
||||
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis plugins folder
|
||||
/// </summary>
|
||||
public static readonly string PluginsFolder = Path.Combine(DataFolder, "Plugins");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis user layouts folder
|
||||
/// </summary>
|
||||
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
|
||||
|
||||
/// <summary>
|
||||
/// The current API version for plugins
|
||||
/// </summary>
|
||||
public static readonly Version PluginApi = new Version(1, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The plugin info used by core components of Artemis
|
||||
/// </summary>
|
||||
public static readonly PluginInfo CorePluginInfo = new()
|
||||
/// <summary>
|
||||
/// The build information related to the currently running Artemis build
|
||||
/// <para>Information is retrieved from <c>buildinfo.json</c></para>
|
||||
/// </summary>
|
||||
public static readonly BuildInfo BuildInfo = File.Exists(Path.Combine(ApplicationFolder, "buildinfo.json"))
|
||||
? JsonConvert.DeserializeObject<BuildInfo>(File.ReadAllText(Path.Combine(ApplicationFolder, "buildinfo.json")))!
|
||||
: new BuildInfo
|
||||
{
|
||||
Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core", Version = new Version(2, 0)
|
||||
IsLocalBuild = true,
|
||||
BuildId = 1337,
|
||||
BuildNumber = 1337,
|
||||
SourceBranch = "local",
|
||||
SourceVersion = "local"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The build information related to the currently running Artemis build
|
||||
/// <para>Information is retrieved from <c>buildinfo.json</c></para>
|
||||
/// </summary>
|
||||
public static readonly BuildInfo BuildInfo = File.Exists(Path.Combine(ApplicationFolder, "buildinfo.json"))
|
||||
? JsonConvert.DeserializeObject<BuildInfo>(File.ReadAllText(Path.Combine(ApplicationFolder, "buildinfo.json")))!
|
||||
: new BuildInfo
|
||||
{
|
||||
IsLocalBuild = true,
|
||||
BuildId = 1337,
|
||||
BuildNumber = 1337,
|
||||
SourceBranch = "local",
|
||||
SourceVersion = "local"
|
||||
};
|
||||
/// <summary>
|
||||
/// The plugin used by core components of Artemis
|
||||
/// </summary>
|
||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
||||
|
||||
/// <summary>
|
||||
/// The plugin used by core components of Artemis
|
||||
/// </summary>
|
||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
||||
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
||||
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||
|
||||
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
||||
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||
internal static JsonSerializerSettings JsonConvertSettings = new()
|
||||
{
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
|
||||
internal static JsonSerializerSettings JsonConvertSettings = new()
|
||||
{
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All,
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
|
||||
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All,
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive numeric types
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Type> NumberTypes = new List<Type>
|
||||
{
|
||||
typeof(sbyte),
|
||||
typeof(byte),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(float),
|
||||
typeof(double),
|
||||
typeof(decimal)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive numeric types
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Type> NumberTypes = new List<Type>
|
||||
{
|
||||
typeof(sbyte),
|
||||
typeof(byte),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(float),
|
||||
typeof(double),
|
||||
typeof(decimal)
|
||||
};
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive integral numeric types
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Type> IntegralNumberTypes = new List<Type>
|
||||
{
|
||||
typeof(sbyte),
|
||||
typeof(byte),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive integral numeric types
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Type> IntegralNumberTypes = new List<Type>
|
||||
{
|
||||
typeof(sbyte),
|
||||
typeof(byte),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong)
|
||||
};
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive floating-point numeric types
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Type> FloatNumberTypes = new List<Type>
|
||||
{
|
||||
typeof(float),
|
||||
typeof(double),
|
||||
typeof(decimal)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive floating-point numeric types
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Type> FloatNumberTypes = new List<Type>
|
||||
{
|
||||
typeof(float),
|
||||
typeof(double),
|
||||
typeof(decimal)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via
|
||||
/// <see cref="IRgbService.UpdateGraphicsContext" />.
|
||||
/// </summary>
|
||||
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via
|
||||
/// <see cref="IRgbService.UpdateGraphicsContext" />.
|
||||
/// </summary>
|
||||
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
|
||||
}
|
||||
@ -1,31 +1,30 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class BoolLayerProperty : LayerProperty<bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class BoolLayerProperty : LayerProperty<bool>
|
||||
internal BoolLayerProperty()
|
||||
{
|
||||
internal BoolLayerProperty()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="BoolLayerProperty" /> to a <see cref="bool" />
|
||||
/// </summary>
|
||||
public static implicit operator bool(BoolLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="BoolLayerProperty" /> to a <see cref="bool" />
|
||||
/// </summary>
|
||||
public static implicit operator bool(BoolLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Boolean properties do not support keyframes.");
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Boolean properties do not support keyframes.");
|
||||
}
|
||||
}
|
||||
@ -1,35 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class EnumLayerProperty<T> : LayerProperty<T> where T : Enum
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class EnumLayerProperty<T> : LayerProperty<T> where T : Enum
|
||||
internal EnumLayerProperty()
|
||||
{
|
||||
internal EnumLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="EnumLayerProperty{T}" /> to a <typeparamref name="T"/>
|
||||
/// </summary>
|
||||
public static implicit operator T(EnumLayerProperty<T> p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="EnumLayerProperty{T}" /> to a <typeparamref name="T" />
|
||||
/// </summary>
|
||||
public static implicit operator T(EnumLayerProperty<T> p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="EnumLayerProperty{T}" /> to an <see cref="int" />
|
||||
/// </summary>
|
||||
public static implicit operator int(EnumLayerProperty<T> p)
|
||||
{
|
||||
return Convert.ToInt32(p.CurrentValue);
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="EnumLayerProperty{T}" /> to an <see cref="int" />
|
||||
/// </summary>
|
||||
public static implicit operator int(EnumLayerProperty<T> p)
|
||||
{
|
||||
return Convert.ToInt32(p.CurrentValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Enum properties do not support keyframes.");
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Enum properties do not support keyframes.");
|
||||
}
|
||||
}
|
||||
@ -1,39 +1,38 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class FloatLayerProperty : LayerProperty<float>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class FloatLayerProperty : LayerProperty<float>
|
||||
internal FloatLayerProperty()
|
||||
{
|
||||
internal FloatLayerProperty()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="FloatLayerProperty" /> to a <see cref="float" />
|
||||
/// </summary>
|
||||
public static implicit operator float(FloatLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="FloatLayerProperty" /> to a <see cref="float" />
|
||||
/// </summary>
|
||||
public static implicit operator float(FloatLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="FloatLayerProperty" /> to a <see cref="double" />
|
||||
/// </summary>
|
||||
public static implicit operator double(FloatLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="FloatLayerProperty" /> to a <see cref="double" />
|
||||
/// </summary>
|
||||
public static implicit operator double(FloatLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float diff = NextKeyframe!.Value - CurrentKeyframe!.Value;
|
||||
CurrentValue = CurrentKeyframe!.Value + diff * keyframeProgressEased;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float diff = NextKeyframe!.Value - CurrentKeyframe!.Value;
|
||||
CurrentValue = CurrentKeyframe!.Value + diff * keyframeProgressEased;
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,23 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class FloatRangeLayerProperty : LayerProperty<FloatRange>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class FloatRangeLayerProperty : LayerProperty<FloatRange>
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue = new FloatRange(value, CurrentValue.End), "Start");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue = new FloatRange(CurrentValue.Start, value), "End");
|
||||
}
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue = new FloatRange(value, CurrentValue.End), "Start");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue = new FloatRange(CurrentValue.Start, value), "End");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
|
||||
float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
|
||||
CurrentValue = new FloatRange(
|
||||
(float) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
|
||||
(float) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
|
||||
);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
|
||||
float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
|
||||
CurrentValue = new FloatRange(
|
||||
CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased,
|
||||
CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,49 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class IntLayerProperty : LayerProperty<int>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class IntLayerProperty : LayerProperty<int>
|
||||
internal IntLayerProperty()
|
||||
{
|
||||
internal IntLayerProperty()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IntLayerProperty" /> to an <see cref="int" />
|
||||
/// </summary>
|
||||
public static implicit operator int(IntLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IntLayerProperty" /> to an <see cref="int" />
|
||||
/// </summary>
|
||||
public static implicit operator int(IntLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IntLayerProperty" /> to a <see cref="float" />
|
||||
/// </summary>
|
||||
public static implicit operator float(IntLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IntLayerProperty" /> to a <see cref="float" />
|
||||
/// </summary>
|
||||
public static implicit operator float(IntLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IntLayerProperty" /> to a <see cref="double" />
|
||||
/// </summary>
|
||||
public static implicit operator double(IntLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IntLayerProperty" /> to a <see cref="double" />
|
||||
/// </summary>
|
||||
public static implicit operator double(IntLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
int diff = NextKeyframe!.Value - CurrentKeyframe!.Value;
|
||||
CurrentValue = (int) Math.Round(CurrentKeyframe!.Value + diff * keyframeProgressEased, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
int diff = NextKeyframe!.Value - CurrentKeyframe!.Value;
|
||||
CurrentValue = (int) Math.Round(CurrentKeyframe!.Value + diff * keyframeProgressEased, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,23 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class IntRangeLayerProperty : LayerProperty<IntRange>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class IntRangeLayerProperty : LayerProperty<IntRange>
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue = new IntRange(value, CurrentValue.End), "Start");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue = new IntRange(CurrentValue.Start, value), "End");
|
||||
}
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue = new IntRange(value, CurrentValue.End), "Start");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue = new IntRange(CurrentValue.Start, value), "End");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
|
||||
float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
|
||||
CurrentValue = new IntRange(
|
||||
(int) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
|
||||
(int) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
|
||||
);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
|
||||
float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
|
||||
CurrentValue = new IntRange(
|
||||
(int) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
|
||||
(int) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,27 +1,26 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A special layer property used to configure the selected layer brush
|
||||
/// </summary>
|
||||
public class LayerBrushReferenceLayerProperty : LayerProperty<LayerBrushReference?>
|
||||
{
|
||||
/// <summary>
|
||||
/// A special layer property used to configure the selected layer brush
|
||||
/// </summary>
|
||||
public class LayerBrushReferenceLayerProperty : LayerProperty<LayerBrushReference?>
|
||||
internal LayerBrushReferenceLayerProperty()
|
||||
{
|
||||
internal LayerBrushReferenceLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="LayerBrushReferenceLayerProperty" /> to an <see cref="LayerBrushReference" />
|
||||
/// </summary>
|
||||
public static implicit operator LayerBrushReference?(LayerBrushReferenceLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="LayerBrushReferenceLayerProperty" /> to an <see cref="LayerBrushReference" />
|
||||
/// </summary>
|
||||
public static implicit operator LayerBrushReference?(LayerBrushReferenceLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Layer brush references do not support keyframes.");
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Layer brush references do not support keyframes.");
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,33 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class SKColorLayerProperty : LayerProperty<SKColor>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class SKColorLayerProperty : LayerProperty<SKColor>
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="SKColorLayerProperty" /> to an <see cref="SKColor" />¶
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <returns></returns>
|
||||
public static implicit operator SKColor(SKColorLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="SKColorLayerProperty" /> to an <see cref="SKColor" />¶
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <returns></returns>
|
||||
public static implicit operator SKColor(SKColorLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
CurrentValue = CurrentKeyframe!.Value.Interpolate(NextKeyframe!.Value, keyframeProgressEased);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
CurrentValue = CurrentKeyframe!.Value.Interpolate(NextKeyframe!.Value, keyframeProgressEased);
|
||||
}
|
||||
}
|
||||
@ -1,35 +1,34 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class SKPointLayerProperty : LayerProperty<SKPoint>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class SKPointLayerProperty : LayerProperty<SKPoint>
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), "X");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), "Y");
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="SKPointLayerProperty" /> to an <see cref="SKPoint" />
|
||||
/// </summary>
|
||||
public static implicit operator SKPoint(SKPointLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="SKPointLayerProperty" /> to an <see cref="SKPoint" />
|
||||
/// </summary>
|
||||
public static implicit operator SKPoint(SKPointLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), "X");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), "Y");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float xDiff = NextKeyframe!.Value.X - CurrentKeyframe!.Value.X;
|
||||
float yDiff = NextKeyframe!.Value.Y - CurrentKeyframe!.Value.Y;
|
||||
CurrentValue = new SKPoint(CurrentKeyframe!.Value.X + xDiff * keyframeProgressEased, CurrentKeyframe!.Value.Y + yDiff * keyframeProgressEased);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float xDiff = NextKeyframe!.Value.X - CurrentKeyframe!.Value.X;
|
||||
float yDiff = NextKeyframe!.Value.Y - CurrentKeyframe!.Value.Y;
|
||||
CurrentValue = new SKPoint(CurrentKeyframe!.Value.X + xDiff * keyframeProgressEased, CurrentKeyframe!.Value.Y + yDiff * keyframeProgressEased);
|
||||
}
|
||||
}
|
||||
@ -1,35 +1,34 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class SKSizeLayerProperty : LayerProperty<SKSize>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class SKSizeLayerProperty : LayerProperty<SKSize>
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), "Width");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), "Height");
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="SKSizeLayerProperty" /> to an <see cref="SKSize" />
|
||||
/// </summary>
|
||||
public static implicit operator SKSize(SKSizeLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="SKSizeLayerProperty" /> to an <see cref="SKSize" />
|
||||
/// </summary>
|
||||
public static implicit operator SKSize(SKSizeLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Width, value => CurrentValue = new SKSize(value, CurrentValue.Height), "Width");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Height, value => CurrentValue = new SKSize(CurrentValue.Width, value), "Height");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float widthDiff = NextKeyframe!.Value.Width - CurrentKeyframe!.Value.Width;
|
||||
float heightDiff = NextKeyframe!.Value.Height - CurrentKeyframe!.Value.Height;
|
||||
CurrentValue = new SKSize(CurrentKeyframe!.Value.Width + widthDiff * keyframeProgressEased, CurrentKeyframe!.Value.Height + heightDiff * keyframeProgressEased);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
float widthDiff = NextKeyframe!.Value.Width - CurrentKeyframe!.Value.Width;
|
||||
float heightDiff = NextKeyframe!.Value.Height - CurrentKeyframe!.Value.Height;
|
||||
CurrentValue = new SKSize(CurrentKeyframe!.Value.Width + widthDiff * keyframeProgressEased, CurrentKeyframe!.Value.Height + heightDiff * keyframeProgressEased);
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about data model path related events
|
||||
/// </summary>
|
||||
public class DataModelPathEventArgs : EventArgs
|
||||
{
|
||||
internal DataModelPathEventArgs(DataModelPath dataModelPath)
|
||||
{
|
||||
DataModelPath = dataModelPath;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model path this event is related to
|
||||
/// </summary>
|
||||
public DataModelPath DataModelPath { get; }
|
||||
/// <summary>
|
||||
/// Provides data about data model path related events
|
||||
/// </summary>
|
||||
public class DataModelPathEventArgs : EventArgs
|
||||
{
|
||||
internal DataModelPathEventArgs(DataModelPath dataModelPath)
|
||||
{
|
||||
DataModelPath = dataModelPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model path this event is related to
|
||||
/// </summary>
|
||||
public DataModelPath DataModelPath { get; }
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about device related events
|
||||
/// </summary>
|
||||
public class DeviceEventArgs : EventArgs
|
||||
{
|
||||
internal DeviceEventArgs(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device this event is related to
|
||||
/// </summary>
|
||||
public ArtemisDevice Device { get; }
|
||||
/// <summary>
|
||||
/// Provides data about device related events
|
||||
/// </summary>
|
||||
public class DeviceEventArgs : EventArgs
|
||||
{
|
||||
internal DeviceEventArgs(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device this event is related to
|
||||
/// </summary>
|
||||
public ArtemisDevice Device { get; }
|
||||
}
|
||||
@ -1,27 +1,26 @@
|
||||
using System;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about dynamic data model child related events
|
||||
/// </summary>
|
||||
public class DynamicDataModelChildEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about dynamic data model child related events
|
||||
/// </summary>
|
||||
public class DynamicDataModelChildEventArgs : EventArgs
|
||||
internal DynamicDataModelChildEventArgs(DynamicChild dynamicChild, string key)
|
||||
{
|
||||
internal DynamicDataModelChildEventArgs(DynamicChild dynamicChild, string key)
|
||||
{
|
||||
DynamicChild = dynamicChild;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dynamic data model child
|
||||
/// </summary>
|
||||
public DynamicChild DynamicChild { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key of the dynamic data model on the parent <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
DynamicChild = dynamicChild;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dynamic data model child
|
||||
/// </summary>
|
||||
public DynamicChild DynamicChild { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key of the dynamic data model on the parent <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
}
|
||||
@ -1,27 +1,26 @@
|
||||
using System;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about frame rendering related events
|
||||
/// </summary>
|
||||
public class FrameRenderedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about frame rendering related events
|
||||
/// </summary>
|
||||
public class FrameRenderedEventArgs : EventArgs
|
||||
internal FrameRenderedEventArgs(SKTexture texture, RGBSurface rgbSurface)
|
||||
{
|
||||
internal FrameRenderedEventArgs(SKTexture texture, RGBSurface rgbSurface)
|
||||
{
|
||||
Texture = texture;
|
||||
RgbSurface = rgbSurface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture used to render this frame
|
||||
/// </summary>
|
||||
public SKTexture Texture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RGB surface used to render this frame
|
||||
/// </summary>
|
||||
public RGBSurface RgbSurface { get; }
|
||||
Texture = texture;
|
||||
RgbSurface = rgbSurface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture used to render this frame
|
||||
/// </summary>
|
||||
public SKTexture Texture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RGB surface used to render this frame
|
||||
/// </summary>
|
||||
public RGBSurface RgbSurface { get; }
|
||||
}
|
||||
@ -2,33 +2,32 @@
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about frame rendered related events
|
||||
/// </summary>
|
||||
public class FrameRenderingEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about frame rendered related events
|
||||
/// </summary>
|
||||
public class FrameRenderingEventArgs : EventArgs
|
||||
internal FrameRenderingEventArgs(SKCanvas canvas, double deltaTime, RGBSurface rgbSurface)
|
||||
{
|
||||
internal FrameRenderingEventArgs(SKCanvas canvas, double deltaTime, RGBSurface rgbSurface)
|
||||
{
|
||||
Canvas = canvas;
|
||||
DeltaTime = deltaTime;
|
||||
RgbSurface = rgbSurface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the canvas this frame is rendering on
|
||||
/// </summary>
|
||||
public SKCanvas Canvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delta time since the last frame was rendered
|
||||
/// </summary>
|
||||
public double DeltaTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RGB surface used to render this frame
|
||||
/// </summary>
|
||||
public RGBSurface RgbSurface { get; }
|
||||
Canvas = canvas;
|
||||
DeltaTime = deltaTime;
|
||||
RgbSurface = rgbSurface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the canvas this frame is rendering on
|
||||
/// </summary>
|
||||
public SKCanvas Canvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delta time since the last frame was rendered
|
||||
/// </summary>
|
||||
public double DeltaTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RGB surface used to render this frame
|
||||
/// </summary>
|
||||
public RGBSurface RgbSurface { get; }
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
using System;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about module events
|
||||
/// </summary>
|
||||
public class ModuleEventArgs : EventArgs
|
||||
{
|
||||
internal ModuleEventArgs(Module module)
|
||||
{
|
||||
Module = module;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module this event is related to
|
||||
/// </summary>
|
||||
public Module Module { get; }
|
||||
/// <summary>
|
||||
/// Provides data about module events
|
||||
/// </summary>
|
||||
public class ModuleEventArgs : EventArgs
|
||||
{
|
||||
internal ModuleEventArgs(Module module)
|
||||
{
|
||||
Module = module;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module this event is related to
|
||||
/// </summary>
|
||||
public Module Module { get; }
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about plugin related events
|
||||
/// </summary>
|
||||
public class PluginEventArgs : EventArgs
|
||||
{
|
||||
internal PluginEventArgs(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin this event is related to
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
/// <summary>
|
||||
/// Provides data about plugin related events
|
||||
/// </summary>
|
||||
public class PluginEventArgs : EventArgs
|
||||
{
|
||||
internal PluginEventArgs(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin this event is related to
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
}
|
||||
@ -1,36 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about plugin feature related events
|
||||
/// </summary>
|
||||
public class PluginFeatureEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about plugin feature related events
|
||||
/// </summary>
|
||||
public class PluginFeatureEventArgs : EventArgs
|
||||
internal PluginFeatureEventArgs(PluginFeature pluginFeature)
|
||||
{
|
||||
internal PluginFeatureEventArgs(PluginFeature pluginFeature)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature this event is related to
|
||||
/// </summary>
|
||||
public PluginFeature PluginFeature { get; }
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about plugin feature info related events
|
||||
/// Gets the plugin feature this event is related to
|
||||
/// </summary>
|
||||
public class PluginFeatureInfoEventArgs : EventArgs
|
||||
{
|
||||
internal PluginFeatureInfoEventArgs(PluginFeatureInfo pluginFeatureInfo)
|
||||
{
|
||||
PluginFeatureInfo = pluginFeatureInfo;
|
||||
}
|
||||
public PluginFeature PluginFeature { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature this event is related to
|
||||
/// </summary>
|
||||
public PluginFeatureInfo PluginFeatureInfo { get; }
|
||||
/// <summary>
|
||||
/// Provides data about plugin feature info related events
|
||||
/// </summary>
|
||||
public class PluginFeatureInfoEventArgs : EventArgs
|
||||
{
|
||||
internal PluginFeatureInfoEventArgs(PluginFeatureInfo pluginFeatureInfo)
|
||||
{
|
||||
PluginFeatureInfo = pluginFeatureInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature this event is related to
|
||||
/// </summary>
|
||||
public PluginFeatureInfo PluginFeatureInfo { get; }
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for data binding events.
|
||||
/// </summary>
|
||||
public class DataBindingEventArgs : EventArgs
|
||||
{
|
||||
internal DataBindingEventArgs(IDataBinding dataBinding)
|
||||
{
|
||||
DataBinding = dataBinding;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding this event is related to
|
||||
/// </summary>
|
||||
public IDataBinding DataBinding { get; }
|
||||
/// <summary>
|
||||
/// Provides data for data binding events.
|
||||
/// </summary>
|
||||
public class DataBindingEventArgs : EventArgs
|
||||
{
|
||||
internal DataBindingEventArgs(IDataBinding dataBinding)
|
||||
{
|
||||
DataBinding = dataBinding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding this event is related to
|
||||
/// </summary>
|
||||
public IDataBinding DataBinding { get; }
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for the <see langword='DataBindingPropertyUpdatedEvent' /> event.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class DataBindingPropertyUpdatedEvent<T> : EventArgs
|
||||
{
|
||||
internal DataBindingPropertyUpdatedEvent(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// The updated value that should be applied to the layer property
|
||||
/// </summary>
|
||||
public T Value { get; }
|
||||
/// <summary>
|
||||
/// Provides data for the <see langword='DataBindingPropertyUpdatedEvent' /> event.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class DataBindingPropertyUpdatedEvent<T> : EventArgs
|
||||
{
|
||||
internal DataBindingPropertyUpdatedEvent(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The updated value that should be applied to the layer property
|
||||
/// </summary>
|
||||
public T Value { get; }
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for layer property events.
|
||||
/// </summary>
|
||||
public class LayerPropertyEventArgs : EventArgs
|
||||
{
|
||||
internal LayerPropertyEventArgs(ILayerProperty layerProperty)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer property this event is related to
|
||||
/// </summary>
|
||||
public ILayerProperty LayerProperty { get; }
|
||||
/// <summary>
|
||||
/// Provides data for layer property events.
|
||||
/// </summary>
|
||||
public class LayerPropertyEventArgs : EventArgs
|
||||
{
|
||||
internal LayerPropertyEventArgs(ILayerProperty layerProperty)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer property this event is related to
|
||||
/// </summary>
|
||||
public ILayerProperty LayerProperty { get; }
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for profile configuration events.
|
||||
/// </summary>
|
||||
public class ProfileConfigurationEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile configuration this event is related to
|
||||
/// </summary>
|
||||
public ProfileConfiguration ProfileConfiguration { get; }
|
||||
/// <summary>
|
||||
/// Provides data for profile configuration events.
|
||||
/// </summary>
|
||||
public class ProfileConfigurationEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile configuration this event is related to
|
||||
/// </summary>
|
||||
public ProfileConfiguration ProfileConfiguration { get; }
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for profile element events.
|
||||
/// </summary>
|
||||
public class ProfileElementEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileElementEventArgs(ProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element this event is related to
|
||||
/// </summary>
|
||||
public ProfileElement ProfileElement { get; }
|
||||
/// <summary>
|
||||
/// Provides data for profile element events.
|
||||
/// </summary>
|
||||
public class ProfileElementEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileElementEventArgs(ProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element this event is related to
|
||||
/// </summary>
|
||||
public ProfileElement ProfileElement { get; }
|
||||
}
|
||||
@ -1,33 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about application restart events
|
||||
/// </summary>
|
||||
public class RestartEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about application restart events
|
||||
/// </summary>
|
||||
public class RestartEventArgs : EventArgs
|
||||
internal RestartEventArgs(bool elevate, TimeSpan delay, List<string>? extraArgs)
|
||||
{
|
||||
internal RestartEventArgs(bool elevate, TimeSpan delay, List<string>? extraArgs)
|
||||
{
|
||||
Elevate = elevate;
|
||||
Delay = delay;
|
||||
ExtraArgs = extraArgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the application should be restarted with elevated permissions
|
||||
/// </summary>
|
||||
public bool Elevate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delay before killing process and restarting
|
||||
/// </summary>
|
||||
public TimeSpan Delay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of extra arguments to pass to Artemis when restarting
|
||||
/// </summary>
|
||||
public List<string>? ExtraArgs { get; }
|
||||
Elevate = elevate;
|
||||
Delay = delay;
|
||||
ExtraArgs = extraArgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the application should be restarted with elevated permissions
|
||||
/// </summary>
|
||||
public bool Elevate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delay before killing process and restarting
|
||||
/// </summary>
|
||||
public TimeSpan Delay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of extra arguments to pass to Artemis when restarting
|
||||
/// </summary>
|
||||
public List<string>? ExtraArgs { get; }
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DataModelStoreEvent
|
||||
{
|
||||
public DataModelStoreEvent(DataModelRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
public DataModelRegistration Registration { get; }
|
||||
internal class DataModelStoreEvent
|
||||
{
|
||||
public DataModelStoreEvent(DataModelRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
|
||||
public DataModelRegistration Registration { get; }
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class LayerBrushStoreEvent
|
||||
{
|
||||
public LayerBrushStoreEvent(LayerBrushRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
public LayerBrushRegistration Registration { get; }
|
||||
internal class LayerBrushStoreEvent
|
||||
{
|
||||
public LayerBrushStoreEvent(LayerBrushRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
|
||||
public LayerBrushRegistration Registration { get; }
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class LayerEffectStoreEvent
|
||||
{
|
||||
public LayerEffectStoreEvent(LayerEffectRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
public LayerEffectRegistration Registration { get; }
|
||||
internal class LayerEffectStoreEvent
|
||||
{
|
||||
public LayerEffectStoreEvent(LayerEffectRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
|
||||
public LayerEffectRegistration Registration { get; }
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NodeTypeStoreEvent
|
||||
{
|
||||
public NodeTypeStoreEvent(NodeTypeRegistration typeRegistration)
|
||||
{
|
||||
TypeRegistration = typeRegistration;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
public NodeTypeRegistration TypeRegistration { get; }
|
||||
internal class NodeTypeStoreEvent
|
||||
{
|
||||
public NodeTypeStoreEvent(NodeTypeRegistration typeRegistration)
|
||||
{
|
||||
TypeRegistration = typeRegistration;
|
||||
}
|
||||
|
||||
public NodeTypeRegistration TypeRegistration { get; }
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about device configuration related events
|
||||
/// </summary>
|
||||
public class SurfaceConfigurationEventArgs : EventArgs
|
||||
{
|
||||
internal SurfaceConfigurationEventArgs(List<ArtemisDevice> devices)
|
||||
{
|
||||
Devices = devices;
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current list of devices
|
||||
/// </summary>
|
||||
public List<ArtemisDevice> Devices { get; }
|
||||
/// <summary>
|
||||
/// Provides data about device configuration related events
|
||||
/// </summary>
|
||||
public class SurfaceConfigurationEventArgs : EventArgs
|
||||
{
|
||||
internal SurfaceConfigurationEventArgs(List<ArtemisDevice> devices)
|
||||
{
|
||||
Devices = devices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current list of devices
|
||||
/// </summary>
|
||||
public List<ArtemisDevice> Devices { get; }
|
||||
}
|
||||
@ -1,18 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur within the Artemis Core
|
||||
/// </summary>
|
||||
public class ArtemisCoreException : Exception
|
||||
{
|
||||
internal ArtemisCoreException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal ArtemisCoreException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents errors that occur within the Artemis Core
|
||||
/// </summary>
|
||||
public class ArtemisCoreException : Exception
|
||||
{
|
||||
internal ArtemisCoreException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
internal ArtemisCoreException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents SkiaSharp graphics-context related errors
|
||||
/// </summary>
|
||||
public class ArtemisGraphicsContextException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents SkiaSharp graphics-context related errors
|
||||
/// </summary>
|
||||
public class ArtemisGraphicsContextException : Exception
|
||||
/// <inheritdoc />
|
||||
public ArtemisGraphicsContextException()
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ArtemisGraphicsContextException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ArtemisGraphicsContextException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public ArtemisGraphicsContextException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,53 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin-related error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin-related error occurs
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public class ArtemisPluginException : Exception
|
||||
public ArtemisPluginException(Plugin plugin)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(Plugin plugin, string message) : base(message)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin the error is related to
|
||||
/// </summary>
|
||||
public Plugin? Plugin { get; }
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(Plugin plugin, string message) : base(message)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
|
||||
/// </summary>
|
||||
public ArtemisPluginException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin the error is related to
|
||||
/// </summary>
|
||||
public Plugin? Plugin { get; }
|
||||
}
|
||||
@ -1,30 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin feature-related error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginFeatureException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin feature-related error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginFeatureException : Exception
|
||||
internal ArtemisPluginFeatureException(PluginFeature pluginFeature)
|
||||
{
|
||||
internal ArtemisPluginFeatureException(PluginFeature pluginFeature)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
internal ArtemisPluginFeatureException(PluginFeature pluginFeature, string message) : base(message)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
internal ArtemisPluginFeatureException(PluginFeature pluginFeature, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature the error is related to
|
||||
/// </summary>
|
||||
public PluginFeature PluginFeature { get; }
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
internal ArtemisPluginFeatureException(PluginFeature pluginFeature, string message) : base(message)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
internal ArtemisPluginFeatureException(PluginFeature pluginFeature, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature the error is related to
|
||||
/// </summary>
|
||||
public PluginFeature PluginFeature { get; }
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin lock file error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginLockException : Exception
|
||||
{
|
||||
internal ArtemisPluginLockException(Exception? innerException) : base(CreateExceptionMessage(innerException), innerException)
|
||||
{
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
private static string CreateExceptionMessage(Exception? innerException)
|
||||
{
|
||||
return innerException != null
|
||||
? "Found a lock file, skipping load, see inner exception for last known exception."
|
||||
: "Found a lock file, skipping load.";
|
||||
}
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin lock file error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginLockException : Exception
|
||||
{
|
||||
internal ArtemisPluginLockException(Exception? innerException) : base(CreateExceptionMessage(innerException), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
private static string CreateExceptionMessage(Exception? innerException)
|
||||
{
|
||||
return innerException != null
|
||||
? "Found a lock file, skipping load, see inner exception for last known exception."
|
||||
: "Found a lock file, skipping load.";
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin prerequisite-related error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginPrerequisiteException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin prerequisite-related error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginPrerequisiteException : Exception
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject)
|
||||
{
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message) : base(message)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subject the error is related to
|
||||
/// </summary>
|
||||
public IPrerequisitesSubject Subject { get; }
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message) : base(message)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subject the error is related to
|
||||
/// </summary>
|
||||
public IPrerequisitesSubject Subject { get; }
|
||||
}
|
||||
@ -1,32 +1,31 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal static class DirectoryInfoExtensions
|
||||
{
|
||||
internal static class DirectoryInfoExtensions
|
||||
public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target)
|
||||
{
|
||||
public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target)
|
||||
foreach (DirectoryInfo dir in source.GetDirectories())
|
||||
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
|
||||
foreach (FileInfo file in source.GetFiles())
|
||||
file.CopyTo(Path.Combine(target.FullName, file.Name));
|
||||
}
|
||||
|
||||
public static void DeleteRecursively(this DirectoryInfo baseDir)
|
||||
{
|
||||
if (!baseDir.Exists)
|
||||
return;
|
||||
|
||||
foreach (DirectoryInfo dir in baseDir.EnumerateDirectories())
|
||||
DeleteRecursively(dir);
|
||||
FileInfo[] files = baseDir.GetFiles();
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
foreach (DirectoryInfo dir in source.GetDirectories())
|
||||
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
|
||||
foreach (FileInfo file in source.GetFiles())
|
||||
file.CopyTo(Path.Combine(target.FullName, file.Name));
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
public static void DeleteRecursively(this DirectoryInfo baseDir)
|
||||
{
|
||||
if (!baseDir.Exists)
|
||||
return;
|
||||
|
||||
foreach (DirectoryInfo dir in baseDir.EnumerateDirectories())
|
||||
DeleteRecursively(dir);
|
||||
FileInfo[] files = baseDir.GetFiles();
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
baseDir.Delete();
|
||||
}
|
||||
baseDir.Delete();
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="double" /> extensions
|
||||
/// </summary>
|
||||
public static class DoubleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="double" /> extensions
|
||||
/// Rounds the provided number away to zero and casts the result to an <see cref="int" />
|
||||
/// </summary>
|
||||
public static class DoubleExtensions
|
||||
/// <param name="number">The number to round</param>
|
||||
/// <returns>The rounded number as an integer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RoundToInt(this double number)
|
||||
{
|
||||
/// <summary>
|
||||
/// Rounds the provided number away to zero and casts the result to an <see cref="int" />
|
||||
/// </summary>
|
||||
/// <param name="number">The number to round</param>
|
||||
/// <returns>The rounded number as an integer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RoundToInt(this double number)
|
||||
{
|
||||
return (int) Math.Round(number, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return (int) Math.Round(number, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="float" /> extensions
|
||||
/// </summary>
|
||||
public static class FloatExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="float" /> extensions
|
||||
/// Rounds the provided number away to zero and casts the result to an <see cref="int" />
|
||||
/// </summary>
|
||||
public static class FloatExtensions
|
||||
/// <param name="number">The number to round</param>
|
||||
/// <returns>The rounded number as an integer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RoundToInt(this float number)
|
||||
{
|
||||
/// <summary>
|
||||
/// Rounds the provided number away to zero and casts the result to an <see cref="int" />
|
||||
/// </summary>
|
||||
/// <param name="number">The number to round</param>
|
||||
/// <returns>The rounded number as an integer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RoundToInt(this float number)
|
||||
{
|
||||
return (int) MathF.Round(number, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return (int) MathF.Round(number, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
@ -19,35 +19,33 @@
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="IEnumerable{T}" /> extensions
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="IEnumerable{T}" /> extensions
|
||||
/// Returns the index of the provided element inside the read only collection
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IEnumerableExtensions
|
||||
/// <typeparam name="T">The type of element to find</typeparam>
|
||||
/// <param name="self">The collection to search in</param>
|
||||
/// <param name="elementToFind">The element to find</param>
|
||||
/// <returns>If found, the index of the element to find; otherwise -1</returns>
|
||||
public static int IndexOf<T>(this IReadOnlyCollection<T> self, T elementToFind)
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the index of the provided element inside the read only collection
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of element to find</typeparam>
|
||||
/// <param name="self">The collection to search in</param>
|
||||
/// <param name="elementToFind">The element to find</param>
|
||||
/// <returns>If found, the index of the element to find; otherwise -1</returns>
|
||||
public static int IndexOf<T>(this IReadOnlyCollection<T> self, T elementToFind)
|
||||
int i = 0;
|
||||
foreach (T element in self)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (T element in self)
|
||||
{
|
||||
if (Equals(element, elementToFind))
|
||||
return i;
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
if (Equals(element, elementToFind))
|
||||
return i;
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -1,41 +1,41 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="Process" /> extensions
|
||||
/// </summary>
|
||||
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class", Justification = "I don't care, piss off")]
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="Process" /> extensions
|
||||
/// Gets the file name of the given process
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1060:Move pinvokes to native methods class", Justification = "I don't care, piss off")]
|
||||
public static class ProcessExtensions
|
||||
/// <param name="p">The process</param>
|
||||
/// <returns>The filename of the given process</returns>
|
||||
public static string GetProcessFilename(this Process p)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the file name of the given process
|
||||
/// </summary>
|
||||
/// <param name="p">The process</param>
|
||||
/// <returns>The filename of the given process</returns>
|
||||
public static string GetProcessFilename(this Process p)
|
||||
{
|
||||
int capacity = 2000;
|
||||
StringBuilder builder = new(capacity);
|
||||
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
|
||||
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty;
|
||||
int capacity = 2000;
|
||||
StringBuilder builder = new(capacity);
|
||||
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
|
||||
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty;
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
|
||||
|
||||
[Flags]
|
||||
private enum ProcessAccessFlags : uint
|
||||
{
|
||||
QueryLimitedInformation = 0x00001000
|
||||
}
|
||||
[Flags]
|
||||
private enum ProcessAccessFlags : uint
|
||||
{
|
||||
QueryLimitedInformation = 0x00001000
|
||||
}
|
||||
}
|
||||
@ -2,39 +2,38 @@
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal static class RgbDeviceExtensions
|
||||
{
|
||||
internal static class RgbDeviceExtensions
|
||||
public static string GetDeviceIdentifier(this IRGBDevice rgbDevice)
|
||||
{
|
||||
public static string GetDeviceIdentifier(this IRGBDevice rgbDevice)
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
builder.Append(rgbDevice.DeviceInfo.DeviceName);
|
||||
builder.Append('-');
|
||||
builder.Append(rgbDevice.DeviceInfo.Manufacturer);
|
||||
builder.Append('-');
|
||||
builder.Append(rgbDevice.DeviceInfo.Model);
|
||||
builder.Append('-');
|
||||
builder.Append(rgbDevice.DeviceInfo.DeviceType);
|
||||
return builder.ToString();
|
||||
}
|
||||
StringBuilder builder = new();
|
||||
builder.Append(rgbDevice.DeviceInfo.DeviceName);
|
||||
builder.Append('-');
|
||||
builder.Append(rgbDevice.DeviceInfo.Manufacturer);
|
||||
builder.Append('-');
|
||||
builder.Append(rgbDevice.DeviceInfo.Model);
|
||||
builder.Append('-');
|
||||
builder.Append(rgbDevice.DeviceInfo.DeviceType);
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class RgbRectangleExtensions
|
||||
{
|
||||
public static SKRect ToSKRect(this Rectangle rectangle)
|
||||
{
|
||||
return SKRect.Create(
|
||||
rectangle.Location.X,
|
||||
rectangle.Location.Y,
|
||||
rectangle.Size.Width,
|
||||
rectangle.Size.Height
|
||||
);
|
||||
}
|
||||
|
||||
internal static class RgbRectangleExtensions
|
||||
public static SKRectI ToSKRectI(this Rectangle rectangle)
|
||||
{
|
||||
public static SKRect ToSKRect(this Rectangle rectangle)
|
||||
{
|
||||
return SKRect.Create(
|
||||
rectangle.Location.X,
|
||||
rectangle.Location.Y,
|
||||
rectangle.Size.Width,
|
||||
rectangle.Size.Height
|
||||
);
|
||||
}
|
||||
|
||||
public static SKRectI ToSKRectI(this Rectangle rectangle)
|
||||
{
|
||||
return SKRectI.Round(ToSKRect(rectangle));
|
||||
}
|
||||
return SKRectI.Round(ToSKRect(rectangle));
|
||||
}
|
||||
}
|
||||
@ -2,77 +2,76 @@
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="SKColor" /> extensions
|
||||
/// </summary>
|
||||
public static class SKColorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="SKColor" /> extensions
|
||||
/// Converts hte SKColor to an RGB.NET color
|
||||
/// </summary>
|
||||
public static class SKColorExtensions
|
||||
/// <param name="color">The color to convert</param>
|
||||
/// <returns>The RGB.NET color</returns>
|
||||
public static Color ToRgbColor(this SKColor color)
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts hte SKColor to an RGB.NET color
|
||||
/// </summary>
|
||||
/// <param name="color">The color to convert</param>
|
||||
/// <returns>The RGB.NET color</returns>
|
||||
public static Color ToRgbColor(this SKColor color)
|
||||
{
|
||||
return new Color(color.Alpha, color.Red, color.Green, color.Blue);
|
||||
}
|
||||
return new Color(color.Alpha, color.Red, color.Green, color.Blue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates a color between the <paramref name="from" /> and <paramref name="to" /> color.
|
||||
/// </summary>
|
||||
/// <param name="from">The first color</param>
|
||||
/// <param name="to">The second color</param>
|
||||
/// <param name="progress">A value between 0 and 1</param>
|
||||
/// <returns>The interpolated color</returns>
|
||||
public static SKColor Interpolate(this SKColor from, SKColor to, float progress)
|
||||
{
|
||||
int redDiff = to.Red - from.Red;
|
||||
int greenDiff = to.Green - from.Green;
|
||||
int blueDiff = to.Blue - from.Blue;
|
||||
int alphaDiff = to.Alpha - from.Alpha;
|
||||
/// <summary>
|
||||
/// Interpolates a color between the <paramref name="from" /> and <paramref name="to" /> color.
|
||||
/// </summary>
|
||||
/// <param name="from">The first color</param>
|
||||
/// <param name="to">The second color</param>
|
||||
/// <param name="progress">A value between 0 and 1</param>
|
||||
/// <returns>The interpolated color</returns>
|
||||
public static SKColor Interpolate(this SKColor from, SKColor to, float progress)
|
||||
{
|
||||
int redDiff = to.Red - from.Red;
|
||||
int greenDiff = to.Green - from.Green;
|
||||
int blueDiff = to.Blue - from.Blue;
|
||||
int alphaDiff = to.Alpha - from.Alpha;
|
||||
|
||||
return new SKColor(
|
||||
ClampToByte(from.Red + redDiff * progress),
|
||||
ClampToByte(from.Green + greenDiff * progress),
|
||||
ClampToByte(from.Blue + blueDiff * progress),
|
||||
ClampToByte(from.Alpha + alphaDiff * progress)
|
||||
);
|
||||
}
|
||||
return new SKColor(
|
||||
ClampToByte(from.Red + redDiff * progress),
|
||||
ClampToByte(from.Green + greenDiff * progress),
|
||||
ClampToByte(from.Blue + blueDiff * progress),
|
||||
ClampToByte(from.Alpha + alphaDiff * progress)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the two colors together
|
||||
/// </summary>
|
||||
/// <param name="a">The first color</param>
|
||||
/// <param name="b">The second color</param>
|
||||
/// <returns>The sum of the two colors</returns>
|
||||
public static SKColor Sum(this SKColor a, SKColor b)
|
||||
{
|
||||
return new SKColor(
|
||||
ClampToByte(a.Red + b.Red),
|
||||
ClampToByte(a.Green + b.Green),
|
||||
ClampToByte(a.Blue + b.Blue),
|
||||
ClampToByte(a.Alpha + b.Alpha)
|
||||
);
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds the two colors together
|
||||
/// </summary>
|
||||
/// <param name="a">The first color</param>
|
||||
/// <param name="b">The second color</param>
|
||||
/// <returns>The sum of the two colors</returns>
|
||||
public static SKColor Sum(this SKColor a, SKColor b)
|
||||
{
|
||||
return new SKColor(
|
||||
ClampToByte(a.Red + b.Red),
|
||||
ClampToByte(a.Green + b.Green),
|
||||
ClampToByte(a.Blue + b.Blue),
|
||||
ClampToByte(a.Alpha + b.Alpha)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens the color by the specified amount
|
||||
/// </summary>
|
||||
/// <param name="c">The color to darken</param>
|
||||
/// <param name="amount">The brightness of the new color</param>
|
||||
/// <returns>The darkened color</returns>
|
||||
public static SKColor Darken(this SKColor c, float amount)
|
||||
{
|
||||
c.ToHsl(out float h, out float s, out float l);
|
||||
l *= 1f - amount;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
/// <summary>
|
||||
/// Darkens the color by the specified amount
|
||||
/// </summary>
|
||||
/// <param name="c">The color to darken</param>
|
||||
/// <param name="amount">The brightness of the new color</param>
|
||||
/// <returns>The darkened color</returns>
|
||||
public static SKColor Darken(this SKColor c, float amount)
|
||||
{
|
||||
c.ToHsl(out float h, out float s, out float l);
|
||||
l *= 1f - amount;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
|
||||
private static byte ClampToByte(float value)
|
||||
{
|
||||
return (byte) Math.Clamp(value, 0, 255);
|
||||
}
|
||||
private static byte ClampToByte(float value)
|
||||
{
|
||||
return (byte) Math.Clamp(value, 0, 255);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,15 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal static class SKPaintExtensions
|
||||
{
|
||||
internal static class SKPaintExtensions
|
||||
internal static void DisposeSelfAndProperties(this SKPaint paint)
|
||||
{
|
||||
internal static void DisposeSelfAndProperties(this SKPaint paint)
|
||||
{
|
||||
paint.ImageFilter?.Dispose();
|
||||
paint.ColorFilter?.Dispose();
|
||||
paint.MaskFilter?.Dispose();
|
||||
paint.Shader?.Dispose();
|
||||
paint.Dispose();
|
||||
}
|
||||
paint.ImageFilter?.Dispose();
|
||||
paint.ColorFilter?.Dispose();
|
||||
paint.MaskFilter?.Dispose();
|
||||
paint.Shader?.Dispose();
|
||||
paint.Dispose();
|
||||
}
|
||||
}
|
||||
@ -25,110 +25,108 @@
|
||||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal static class StreamExtensions
|
||||
{
|
||||
internal static class StreamExtensions
|
||||
private const int DefaultBufferSize = 81920;
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="bufferSize">The size of the copy block buffer</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static async Task CopyToAsync(
|
||||
this Stream source,
|
||||
long sourceLength,
|
||||
Stream destination,
|
||||
int bufferSize,
|
||||
IProgress<(long, long)> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private const int DefaultBufferSize = 81920;
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (!source.CanRead)
|
||||
throw new ArgumentException("Has to be readable", nameof(source));
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
if (!destination.CanWrite)
|
||||
throw new ArgumentException("Has to be writable", nameof(destination));
|
||||
if (bufferSize <= 0)
|
||||
bufferSize = DefaultBufferSize;
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="bufferSize">The size of the copy block buffer</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static async Task CopyToAsync(
|
||||
this Stream source,
|
||||
long sourceLength,
|
||||
Stream destination,
|
||||
int bufferSize,
|
||||
IProgress<(long, long)> progress,
|
||||
CancellationToken cancellationToken)
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (!source.CanRead)
|
||||
throw new ArgumentException("Has to be readable", nameof(source));
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
if (!destination.CanWrite)
|
||||
throw new ArgumentException("Has to be writable", nameof(destination));
|
||||
if (bufferSize <= 0)
|
||||
bufferSize = DefaultBufferSize;
|
||||
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report((totalBytesRead, sourceLength));
|
||||
}
|
||||
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report((totalBytesRead, sourceLength));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyToAsync(source, sourceLength, destination, 0, progress, cancellationToken);
|
||||
}
|
||||
progress?.Report((totalBytesRead, sourceLength));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyToAsync(source, 0L, destination, 0, progress, cancellationToken);
|
||||
}
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyToAsync(source, sourceLength, destination, 0, progress, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress)
|
||||
{
|
||||
return CopyToAsync(source, sourceLength, destination, 0, progress, default);
|
||||
}
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyToAsync(source, 0L, destination, 0, progress, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress)
|
||||
{
|
||||
return CopyToAsync(source, 0L, destination, 0, progress, default);
|
||||
}
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress)
|
||||
{
|
||||
return CopyToAsync(source, sourceLength, destination, 0, progress, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress)
|
||||
{
|
||||
return CopyToAsync(source, 0L, destination, 0, progress, default);
|
||||
}
|
||||
}
|
||||
@ -5,249 +5,250 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="Type" /> extensions
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="Type" /> extensions
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
private static readonly Dictionary<Type, List<Type>> PrimitiveTypeConversions = new()
|
||||
{
|
||||
private static readonly Dictionary<Type, List<Type>> PrimitiveTypeConversions = new()
|
||||
{
|
||||
{typeof(decimal), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char)}},
|
||||
{typeof(double), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float)}},
|
||||
{typeof(float), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float)}},
|
||||
{typeof(ulong), new List<Type> {typeof(byte), typeof(ushort), typeof(uint), typeof(char)}},
|
||||
{typeof(long), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char)}},
|
||||
{typeof(uint), new List<Type> {typeof(byte), typeof(ushort), typeof(char)}},
|
||||
{typeof(int), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char)}},
|
||||
{typeof(ushort), new List<Type> {typeof(byte), typeof(char)}},
|
||||
{typeof(short), new List<Type> {typeof(byte)}}
|
||||
};
|
||||
{typeof(decimal), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char)}},
|
||||
{typeof(double), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float)}},
|
||||
{typeof(float), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float)}},
|
||||
{typeof(ulong), new List<Type> {typeof(byte), typeof(ushort), typeof(uint), typeof(char)}},
|
||||
{typeof(long), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char)}},
|
||||
{typeof(uint), new List<Type> {typeof(byte), typeof(ushort), typeof(char)}},
|
||||
{typeof(int), new List<Type> {typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char)}},
|
||||
{typeof(ushort), new List<Type> {typeof(byte), typeof(char)}},
|
||||
{typeof(short), new List<Type> {typeof(byte)}}
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Type, string> TypeKeywords = new()
|
||||
{
|
||||
{typeof(bool), "bool"},
|
||||
{typeof(byte), "byte"},
|
||||
{typeof(sbyte), "sbyte"},
|
||||
{typeof(char), "char"},
|
||||
{typeof(decimal), "decimal"},
|
||||
{typeof(double), "double"},
|
||||
{typeof(float), "float"},
|
||||
{typeof(int), "int"},
|
||||
{typeof(uint), "uint"},
|
||||
{typeof(long), "long"},
|
||||
{typeof(ulong), "ulong"},
|
||||
{typeof(short), "short"},
|
||||
{typeof(ushort), "ushort"},
|
||||
{typeof(object), "object"},
|
||||
{typeof(string), "string"}
|
||||
};
|
||||
private static readonly Dictionary<Type, string> TypeKeywords = new()
|
||||
{
|
||||
{typeof(bool), "bool"},
|
||||
{typeof(byte), "byte"},
|
||||
{typeof(sbyte), "sbyte"},
|
||||
{typeof(char), "char"},
|
||||
{typeof(decimal), "decimal"},
|
||||
{typeof(double), "double"},
|
||||
{typeof(float), "float"},
|
||||
{typeof(int), "int"},
|
||||
{typeof(uint), "uint"},
|
||||
{typeof(long), "long"},
|
||||
{typeof(ulong), "ulong"},
|
||||
{typeof(short), "short"},
|
||||
{typeof(ushort), "ushort"},
|
||||
{typeof(object), "object"},
|
||||
{typeof(string), "string"}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided type is of a specified generic type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <param name="genericType">The generic type to match</param>
|
||||
/// <returns>True if the <paramref name="type" /> is generic and of generic type <paramref name="genericType" /></returns>
|
||||
public static bool IsGenericType(this Type? type, Type genericType)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
/// <summary>
|
||||
/// Determines whether the provided type is of a specified generic type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <param name="genericType">The generic type to match</param>
|
||||
/// <returns>True if the <paramref name="type" /> is generic and of generic type <paramref name="genericType" /></returns>
|
||||
public static bool IsGenericType(this Type? type, Type genericType)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.BaseType?.GetGenericTypeDefinition() == genericType;
|
||||
}
|
||||
return type.BaseType?.GetGenericTypeDefinition() == genericType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided type is a struct
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <returns><see langword="true" /> if the type is a struct, otherwise <see langword="false" /></returns>
|
||||
public static bool IsStruct(this Type type)
|
||||
{
|
||||
return type.IsValueType && !type.IsPrimitive && !type.IsEnum;
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines whether the provided type is a struct
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <returns><see langword="true" /> if the type is a struct, otherwise <see langword="false" /></returns>
|
||||
public static bool IsStruct(this Type type)
|
||||
{
|
||||
return type.IsValueType && !type.IsPrimitive && !type.IsEnum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided type is any kind of numeric type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <returns><see langword="true" /> if the type a numeric type, otherwise <see langword="false" /></returns>
|
||||
public static bool TypeIsNumber(this Type type)
|
||||
{
|
||||
return type == typeof(sbyte)
|
||||
|| type == typeof(byte)
|
||||
|| type == typeof(short)
|
||||
|| type == typeof(ushort)
|
||||
|| type == typeof(int)
|
||||
|| type == typeof(uint)
|
||||
|| type == typeof(long)
|
||||
|| type == typeof(ulong)
|
||||
|| type == typeof(float)
|
||||
|| type == typeof(double)
|
||||
|| type == typeof(decimal);
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines whether the provided type is any kind of numeric type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <returns><see langword="true" /> if the type a numeric type, otherwise <see langword="false" /></returns>
|
||||
public static bool TypeIsNumber(this Type type)
|
||||
{
|
||||
return type == typeof(sbyte)
|
||||
|| type == typeof(byte)
|
||||
|| type == typeof(short)
|
||||
|| type == typeof(ushort)
|
||||
|| type == typeof(int)
|
||||
|| type == typeof(uint)
|
||||
|| type == typeof(long)
|
||||
|| type == typeof(ulong)
|
||||
|| type == typeof(float)
|
||||
|| type == typeof(double)
|
||||
|| type == typeof(decimal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided value is any kind of numeric type
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <returns><see langword="true" /> if the value is of a numeric type, otherwise <see langword="false" /></returns>
|
||||
public static bool IsNumber([NotNullWhenAttribute(true)] this object? value)
|
||||
{
|
||||
return value is sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal;
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines whether the provided value is any kind of numeric type
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <returns><see langword="true" /> if the value is of a numeric type, otherwise <see langword="false" /></returns>
|
||||
public static bool IsNumber([NotNullWhenAttribute(true)] this object? value)
|
||||
{
|
||||
return value is sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal;
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/2224421/5015269 but inverted and renamed to match similar framework methods
|
||||
/// <summary>
|
||||
/// Determines whether an instance of a specified type can be casted to a variable of the current type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsCastableFrom(this Type to, Type from)
|
||||
{
|
||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||
return true;
|
||||
if (to.IsAssignableFrom(from))
|
||||
return true;
|
||||
if (PrimitiveTypeConversions.ContainsKey(to) && PrimitiveTypeConversions[to].Contains(from))
|
||||
return true;
|
||||
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
|
||||
return castable;
|
||||
}
|
||||
// From https://stackoverflow.com/a/2224421/5015269 but inverted and renamed to match similar framework methods
|
||||
/// <summary>
|
||||
/// Determines whether an instance of a specified type can be casted to a variable of the current type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsCastableFrom(this Type to, Type from)
|
||||
{
|
||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||
return true;
|
||||
if (to.IsAssignableFrom(from))
|
||||
return true;
|
||||
if (PrimitiveTypeConversions.ContainsKey(to) && PrimitiveTypeConversions[to].Contains(from))
|
||||
return true;
|
||||
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
|
||||
return castable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scores how well the two types can be casted from one to another, 5 being a perfect match and 0 being not castable
|
||||
/// at all
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int ScoreCastability(this Type to, Type? from)
|
||||
{
|
||||
if (from == null)
|
||||
return 0;
|
||||
|
||||
if (to == from)
|
||||
return 5;
|
||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||
return 4;
|
||||
if (PrimitiveTypeConversions.ContainsKey(to) && PrimitiveTypeConversions[to].Contains(from))
|
||||
return 3;
|
||||
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
|
||||
if (castable)
|
||||
return 2;
|
||||
if (to.IsAssignableFrom(from))
|
||||
return 1;
|
||||
/// <summary>
|
||||
/// Scores how well the two types can be casted from one to another, 5 being a perfect match and 0 being not castable
|
||||
/// at all
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int ScoreCastability(this Type to, Type? from)
|
||||
{
|
||||
if (from == null)
|
||||
return 0;
|
||||
|
||||
if (to == from)
|
||||
return 5;
|
||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||
return 4;
|
||||
if (PrimitiveTypeConversions.ContainsKey(to) && PrimitiveTypeConversions[to].Contains(from))
|
||||
return 3;
|
||||
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
|
||||
if (castable)
|
||||
return 2;
|
||||
if (to.IsAssignableFrom(from))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default value of the given type
|
||||
/// </summary>
|
||||
public static object? GetDefault(this Type type)
|
||||
{
|
||||
return type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given type is a generic enumerable
|
||||
/// </summary>
|
||||
public static bool IsGenericEnumerable(this Type type)
|
||||
{
|
||||
// String is an IEnumerable to be fair, but not for us
|
||||
if (type == typeof(string))
|
||||
return false;
|
||||
// It may actually be one instead of implementing one ;)
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
return true;
|
||||
|
||||
return type.GetInterfaces().Any(x =>
|
||||
x.IsGenericType &&
|
||||
x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the type of the provided generic enumerable type
|
||||
/// </summary>
|
||||
public static Type? GetGenericEnumerableType(this Type type)
|
||||
{
|
||||
// It may actually be one instead of implementing one ;)
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
return type.GenericTypeArguments[0];
|
||||
|
||||
Type? enumerableType = type.GetInterfaces().FirstOrDefault(x =>
|
||||
x.IsGenericType &&
|
||||
x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
|
||||
return enumerableType?.GenericTypeArguments[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the <paramref name="typeToCheck"></paramref> is of a certain <paramref name="genericType" />.
|
||||
/// </summary>
|
||||
/// <param name="typeToCheck">The type to check.</param>
|
||||
/// <param name="genericType">The generic type it should be or implement</param>
|
||||
public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
|
||||
{
|
||||
return typeToCheck.IsOfGenericType(genericType, out Type? _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a display name for the given type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to determine the name for</param>
|
||||
/// <param name="humanize">Whether or not to humanize the result, defaults to false</param>
|
||||
/// <returns></returns>
|
||||
public static string GetDisplayName(this Type type, bool humanize = false)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
string displayValue = TypeKeywords.TryGetValue(type, out string? keyword) ? keyword! : type.Name;
|
||||
return humanize ? displayValue.Humanize() : displayValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default value of the given type
|
||||
/// </summary>
|
||||
public static object? GetDefault(this Type type)
|
||||
{
|
||||
return type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
Type genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
if (genericTypeDefinition == typeof(Nullable<>))
|
||||
return type.GenericTypeArguments[0].GetDisplayName(humanize) + "?";
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given type is a generic enumerable
|
||||
/// </summary>
|
||||
public static bool IsGenericEnumerable(this Type type)
|
||||
string stripped = genericTypeDefinition.Name.Split('`')[0];
|
||||
return $"{stripped}<{string.Join(", ", type.GenericTypeArguments.Select(t => t.GetDisplayName(humanize)))}>";
|
||||
}
|
||||
|
||||
private static bool IsOfGenericType(this Type? typeToCheck, Type genericType, out Type? concreteGenericType)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// String is an IEnumerable to be fair, but not for us
|
||||
if (type == typeof(string))
|
||||
concreteGenericType = null;
|
||||
|
||||
if (genericType == null)
|
||||
throw new ArgumentNullException(nameof(genericType));
|
||||
|
||||
if (!genericType.IsGenericTypeDefinition)
|
||||
throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
|
||||
|
||||
if (typeToCheck == null || typeToCheck == typeof(object))
|
||||
return false;
|
||||
// It may actually be one instead of implementing one ;)
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
|
||||
if (typeToCheck == genericType)
|
||||
{
|
||||
concreteGenericType = typeToCheck;
|
||||
return true;
|
||||
|
||||
return type.GetInterfaces().Any(x =>
|
||||
x.IsGenericType &&
|
||||
x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the type of the provided generic enumerable type
|
||||
/// </summary>
|
||||
public static Type? GetGenericEnumerableType(this Type type)
|
||||
{
|
||||
// It may actually be one instead of implementing one ;)
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
return type.GenericTypeArguments[0];
|
||||
|
||||
Type? enumerableType = type.GetInterfaces().FirstOrDefault(x =>
|
||||
x.IsGenericType &&
|
||||
x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
|
||||
return enumerableType?.GenericTypeArguments[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the <paramref name="typeToCheck"></paramref> is of a certain <paramref name="genericType"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeToCheck">The type to check.</param>
|
||||
/// <param name="genericType">The generic type it should be or implement</param>
|
||||
public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
|
||||
{
|
||||
return typeToCheck.IsOfGenericType(genericType, out Type? _);
|
||||
}
|
||||
|
||||
private static bool IsOfGenericType(this Type? typeToCheck, Type genericType, out Type? concreteGenericType)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
concreteGenericType = null;
|
||||
|
||||
if (genericType == null)
|
||||
throw new ArgumentNullException(nameof(genericType));
|
||||
|
||||
if (!genericType.IsGenericTypeDefinition)
|
||||
throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
|
||||
|
||||
if (typeToCheck == null || typeToCheck == typeof(object))
|
||||
return false;
|
||||
|
||||
if (typeToCheck == genericType)
|
||||
{
|
||||
concreteGenericType = typeToCheck;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
|
||||
{
|
||||
concreteGenericType = typeToCheck;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericType.IsInterface)
|
||||
foreach (Type i in typeToCheck.GetInterfaces())
|
||||
if (i.IsOfGenericType(genericType, out concreteGenericType))
|
||||
return true;
|
||||
|
||||
typeToCheck = typeToCheck.BaseType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a display name for the given type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to determine the name for</param>
|
||||
/// <param name="humanize">Whether or not to humanize the result, defaults to false</param>
|
||||
/// <returns></returns>
|
||||
public static string GetDisplayName(this Type type, bool humanize = false)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
string displayValue = TypeKeywords.TryGetValue(type, out string? keyword) ? keyword! : type.Name;
|
||||
return humanize ? displayValue.Humanize() : displayValue;
|
||||
}
|
||||
|
||||
Type genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
if (genericTypeDefinition == typeof(Nullable<>))
|
||||
return type.GenericTypeArguments[0].GetDisplayName(humanize) + "?";
|
||||
if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
|
||||
{
|
||||
concreteGenericType = typeToCheck;
|
||||
return true;
|
||||
}
|
||||
|
||||
string stripped = genericTypeDefinition.Name.Split('`')[0];
|
||||
return $"{stripped}<{string.Join(", ", type.GenericTypeArguments.Select(t => t.GetDisplayName(humanize)))}>";
|
||||
if (genericType.IsInterface)
|
||||
foreach (Type i in typeToCheck.GetInterfaces())
|
||||
{
|
||||
if (i.IsOfGenericType(genericType, out concreteGenericType))
|
||||
return true;
|
||||
}
|
||||
|
||||
typeToCheck = typeToCheck.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,32 +2,31 @@ using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Artemis.Core.JsonConverters
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
/// <summary>
|
||||
/// An int converter that, if required, will round float values
|
||||
/// </summary>
|
||||
internal class ForgivingIntConverter : JsonConverter<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// An int converter that, if required, will round float values
|
||||
/// </summary>
|
||||
internal class ForgivingIntConverter : JsonConverter<int>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
|
||||
{
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
JValue? jsonValue = serializer.Deserialize<JValue>(reader);
|
||||
if (jsonValue == null)
|
||||
throw new JsonReaderException("Failed to deserialize forgiving int value");
|
||||
|
||||
if (jsonValue.Type == JTokenType.Float)
|
||||
return (int) Math.Round(jsonValue.Value<double>());
|
||||
if (jsonValue.Type == JTokenType.Integer)
|
||||
return jsonValue.Value<int>();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
JValue? jsonValue = serializer.Deserialize<JValue>(reader);
|
||||
if (jsonValue == null)
|
||||
throw new JsonReaderException("Failed to deserialize forgiving int value");
|
||||
}
|
||||
|
||||
if (jsonValue.Type == JTokenType.Float)
|
||||
return (int) Math.Round(jsonValue.Value<double>());
|
||||
if (jsonValue.Type == JTokenType.Integer)
|
||||
return jsonValue.Value<int>();
|
||||
|
||||
throw new JsonReaderException("Failed to deserialize forgiving int value");
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,24 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.JsonConverters
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
internal class NumericJsonConverter : JsonConverter<Numeric>
|
||||
{
|
||||
internal class NumericJsonConverter : JsonConverter<Numeric>
|
||||
#region Overrides of JsonConverter<Numeric>
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
|
||||
{
|
||||
#region Overrides of JsonConverter<Numeric>
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
|
||||
{
|
||||
float floatValue = value;
|
||||
writer.WriteValue(floatValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
return new Numeric(reader.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
float floatValue = value;
|
||||
writer.WriteValue(floatValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
return new Numeric(reader.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -2,21 +2,20 @@
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.JsonConverters
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
internal class SKColorConverter : JsonConverter<SKColor>
|
||||
{
|
||||
internal class SKColorConverter : JsonConverter<SKColor>
|
||||
public override void WriteJson(JsonWriter writer, SKColor value, JsonSerializer serializer)
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, SKColor value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
|
||||
public override SKColor ReadJson(JsonReader reader, Type objectType, SKColor existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is string value && !string.IsNullOrWhiteSpace(value))
|
||||
return SKColor.Parse(value);
|
||||
public override SKColor ReadJson(JsonReader reader, Type objectType, SKColor existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is string value && !string.IsNullOrWhiteSpace(value))
|
||||
return SKColor.Parse(value);
|
||||
|
||||
return SKColor.Empty;
|
||||
}
|
||||
return SKColor.Empty;
|
||||
}
|
||||
}
|
||||
@ -2,44 +2,43 @@
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.JsonConverters
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class StreamConverter : JsonConverter<Stream>
|
||||
{
|
||||
#region Overrides of JsonConverter<Stream>
|
||||
|
||||
/// <inheritdoc />
|
||||
public class StreamConverter : JsonConverter<Stream>
|
||||
public override void WriteJson(JsonWriter writer, Stream? value, JsonSerializer serializer)
|
||||
{
|
||||
#region Overrides of JsonConverter<Stream>
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, Stream? value, JsonSerializer serializer)
|
||||
if (value == null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
value.Position = 0;
|
||||
value.CopyTo(memoryStream);
|
||||
writer.WriteValue(memoryStream.ToArray());
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Stream? ReadJson(JsonReader reader, Type objectType, Stream? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is not string base64)
|
||||
return null;
|
||||
|
||||
if (existingValue == null || !hasExistingValue || !existingValue.CanRead)
|
||||
return new MemoryStream(Convert.FromBase64String(base64));
|
||||
|
||||
using MemoryStream memoryStream = new(Convert.FromBase64String(base64));
|
||||
existingValue.Position = 0;
|
||||
memoryStream.CopyTo(existingValue);
|
||||
existingValue.Position = 0;
|
||||
return existingValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
using MemoryStream memoryStream = new();
|
||||
value.Position = 0;
|
||||
value.CopyTo(memoryStream);
|
||||
writer.WriteValue(memoryStream.ToArray());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Stream? ReadJson(JsonReader reader, Type objectType, Stream? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is not string base64)
|
||||
return null;
|
||||
|
||||
if (existingValue == null || !hasExistingValue || !existingValue.CanRead)
|
||||
return new MemoryStream(Convert.FromBase64String(base64));
|
||||
|
||||
using MemoryStream memoryStream = new(Convert.FromBase64String(base64));
|
||||
existingValue.Position = 0;
|
||||
memoryStream.CopyTo(existingValue);
|
||||
existingValue.Position = 0;
|
||||
return existingValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -2,73 +2,68 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Artemis.Core.Properties;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a basic bindable class which notifies when a property value changes.
|
||||
/// </summary>
|
||||
public abstract class CorePropertyChanged : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic bindable class which notifies when a property value changes.
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public abstract class CorePropertyChanged : INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
#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></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool RequiresUpdate<T>(ref T storage, T value)
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <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></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool RequiresUpdate<T>(ref T storage, T value)
|
||||
{
|
||||
return !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>
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected bool SetAndNotify<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 void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
return !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>
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected bool SetAndNotify<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 void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,92 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a default implementation for models that can have a broken state
|
||||
/// </summary>
|
||||
public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
|
||||
{
|
||||
private string? _brokenState;
|
||||
private Exception? _brokenStateException;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a default implementation for models that can have a broken state
|
||||
/// Invokes the <see cref="BrokenStateChanged" /> event
|
||||
/// </summary>
|
||||
public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
|
||||
protected virtual void OnBrokenStateChanged()
|
||||
{
|
||||
private string? _brokenState;
|
||||
private Exception? _brokenStateException;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="BrokenStateChanged" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnBrokenStateChanged()
|
||||
{
|
||||
BrokenStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string BrokenDisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the broken state of this breakable model, if <see langword="null" /> this model is not broken.
|
||||
/// <para>Note: If setting this manually you are also responsible for invoking <see cref="BrokenStateChanged" /></para>
|
||||
/// </summary>
|
||||
public string? BrokenState
|
||||
{
|
||||
get => _brokenState;
|
||||
set => SetAndNotify(ref _brokenState, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception that caused the broken state
|
||||
/// <para>Note: If setting this manually you are also responsible for invoking <see cref="BrokenStateChanged" /></para>
|
||||
/// </summary>
|
||||
public Exception? BrokenStateException
|
||||
{
|
||||
get => _brokenStateException;
|
||||
set => SetAndNotify(ref _brokenStateException, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryOrBreak(Action action, string breakMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
ClearBrokenState(breakMessage);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetBrokenState(breakMessage, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetBrokenState(string state, Exception? exception)
|
||||
{
|
||||
BrokenState = state ?? throw new ArgumentNullException(nameof(state));
|
||||
BrokenStateException = exception;
|
||||
OnBrokenStateChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearBrokenState(string state)
|
||||
{
|
||||
if (state == null) throw new ArgumentNullException(nameof(state));
|
||||
if (BrokenState == null)
|
||||
return;
|
||||
|
||||
if (BrokenState != state) return;
|
||||
BrokenState = null;
|
||||
BrokenStateException = null;
|
||||
OnBrokenStateChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IBreakableModel> GetBrokenHierarchy()
|
||||
{
|
||||
if (BrokenState != null)
|
||||
yield return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? BrokenStateChanged;
|
||||
BrokenStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string BrokenDisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the broken state of this breakable model, if <see langword="null" /> this model is not broken.
|
||||
/// <para>Note: If setting this manually you are also responsible for invoking <see cref="BrokenStateChanged" /></para>
|
||||
/// </summary>
|
||||
public string? BrokenState
|
||||
{
|
||||
get => _brokenState;
|
||||
set => SetAndNotify(ref _brokenState, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception that caused the broken state
|
||||
/// <para>Note: If setting this manually you are also responsible for invoking <see cref="BrokenStateChanged" /></para>
|
||||
/// </summary>
|
||||
public Exception? BrokenStateException
|
||||
{
|
||||
get => _brokenStateException;
|
||||
set => SetAndNotify(ref _brokenStateException, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryOrBreak(Action action, string breakMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
ClearBrokenState(breakMessage);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetBrokenState(breakMessage, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetBrokenState(string state, Exception? exception)
|
||||
{
|
||||
BrokenState = state ?? throw new ArgumentNullException(nameof(state));
|
||||
BrokenStateException = exception;
|
||||
OnBrokenStateChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearBrokenState(string state)
|
||||
{
|
||||
if (state == null) throw new ArgumentNullException(nameof(state));
|
||||
if (BrokenState == null)
|
||||
return;
|
||||
|
||||
if (BrokenState != state) return;
|
||||
BrokenState = null;
|
||||
BrokenStateException = null;
|
||||
OnBrokenStateChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IBreakableModel> GetBrokenHierarchy()
|
||||
{
|
||||
if (BrokenState != null)
|
||||
yield return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? BrokenStateChanged;
|
||||
}
|
||||
@ -1,59 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a model that can have a broken state
|
||||
/// </summary>
|
||||
public interface IBreakableModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a model that can have a broken state
|
||||
/// Gets the display name of this breakable model
|
||||
/// </summary>
|
||||
public interface IBreakableModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the display name of this breakable model
|
||||
/// </summary>
|
||||
string BrokenDisplayName { get; }
|
||||
string BrokenDisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the broken state of this breakable model, if <see langword="null" /> this model is not broken.
|
||||
/// </summary>
|
||||
string? BrokenState { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the broken state of this breakable model, if <see langword="null" /> this model is not broken.
|
||||
/// </summary>
|
||||
string? BrokenState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception that caused the broken state
|
||||
/// </summary>
|
||||
Exception? BrokenStateException { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the exception that caused the broken state
|
||||
/// </summary>
|
||||
Exception? BrokenStateException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Try to execute the provided action. If the action succeeded the broken state is cleared if it matches
|
||||
/// <see paramref="breakMessage" />, if the action throws an exception <see cref="BrokenState" /> and
|
||||
/// <see cref="BrokenStateException" /> are set accordingly.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to attempt to execute</param>
|
||||
/// <param name="breakMessage">The message to clear on succeed or set on failure (exception)</param>
|
||||
/// <returns><see langword="true" /> if the action succeeded; otherwise <see langword="false" />.</returns>
|
||||
bool TryOrBreak(Action action, string breakMessage);
|
||||
/// <summary>
|
||||
/// Try to execute the provided action. If the action succeeded the broken state is cleared if it matches
|
||||
/// <see paramref="breakMessage" />, if the action throws an exception <see cref="BrokenState" /> and
|
||||
/// <see cref="BrokenStateException" /> are set accordingly.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to attempt to execute</param>
|
||||
/// <param name="breakMessage">The message to clear on succeed or set on failure (exception)</param>
|
||||
/// <returns><see langword="true" /> if the action succeeded; otherwise <see langword="false" />.</returns>
|
||||
bool TryOrBreak(Action action, string breakMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the broken state to the provided state and optional exception.
|
||||
/// </summary>
|
||||
/// <param name="state">The state to set the broken state to</param>
|
||||
/// <param name="exception">The exception that caused the broken state</param>
|
||||
public void SetBrokenState(string state, Exception? exception);
|
||||
/// <summary>
|
||||
/// Sets the broken state to the provided state and optional exception.
|
||||
/// </summary>
|
||||
/// <param name="state">The state to set the broken state to</param>
|
||||
/// <param name="exception">The exception that caused the broken state</param>
|
||||
public void SetBrokenState(string state, Exception? exception);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the broken state and exception if <see cref="BrokenState" /> equals <see paramref="state"></see>.
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public void ClearBrokenState(string state);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list containing all broken models, including self and any children
|
||||
/// </summary>
|
||||
IEnumerable<IBreakableModel> GetBrokenHierarchy();
|
||||
/// <summary>
|
||||
/// Clears the broken state and exception if <see cref="BrokenState" /> equals <see paramref="state"></see>.
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public void ClearBrokenState(string state);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the broken state of this model changes
|
||||
/// </summary>
|
||||
event EventHandler BrokenStateChanged;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a list containing all broken models, including self and any children
|
||||
/// </summary>
|
||||
IEnumerable<IBreakableModel> GetBrokenHierarchy();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the broken state of this model changes
|
||||
/// </summary>
|
||||
event EventHandler BrokenStateChanged;
|
||||
}
|
||||
@ -1,18 +1,17 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a model that can be loaded and saved to persistent storage
|
||||
/// </summary>
|
||||
public interface IStorageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a model that can be loaded and saved to persistent storage
|
||||
/// Loads the model from its associated entity
|
||||
/// </summary>
|
||||
public interface IStorageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the model from its associated entity
|
||||
/// </summary>
|
||||
void Load();
|
||||
void Load();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the model to its associated entity
|
||||
/// </summary>
|
||||
void Save();
|
||||
}
|
||||
/// <summary>
|
||||
/// Saves the model to its associated entity
|
||||
/// </summary>
|
||||
void Save();
|
||||
}
|
||||
@ -1,14 +1,13 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a model that updates using a delta time
|
||||
/// </summary>
|
||||
public interface IUpdateModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a model that updates using a delta time
|
||||
/// Performs an update on the model
|
||||
/// </summary>
|
||||
public interface IUpdateModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs an update on the model
|
||||
/// </summary>
|
||||
/// <param name="timeline">The timeline to apply during update</param>
|
||||
void Update(Timeline timeline);
|
||||
}
|
||||
/// <param name="timeline">The timeline to apply during update</param>
|
||||
void Update(Timeline timeline);
|
||||
}
|
||||
@ -2,98 +2,97 @@
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain category of devices
|
||||
/// </summary>
|
||||
public class CategoryAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private int _amount;
|
||||
private DeviceCategory _category;
|
||||
private bool _limitAmount;
|
||||
private int _skip;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain category of devices
|
||||
/// Creates a new instance of the <see cref="CategoryAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public class CategoryAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
public CategoryAdaptionHint()
|
||||
{
|
||||
private DeviceCategory _category;
|
||||
private int _skip;
|
||||
private bool _limitAmount;
|
||||
private int _amount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CategoryAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public CategoryAdaptionHint()
|
||||
{
|
||||
}
|
||||
|
||||
internal CategoryAdaptionHint(CategoryAdaptionHintEntity entity)
|
||||
{
|
||||
Category = (DeviceCategory) entity.Category;
|
||||
Skip = entity.Skip;
|
||||
LimitAmount = entity.LimitAmount;
|
||||
Amount = entity.Amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category of devices LEDs will be applied to
|
||||
/// </summary>
|
||||
public DeviceCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set => SetAndNotify(ref _category, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Category adaption - {nameof(Category)}: {Category}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
IEnumerable<ArtemisDevice> matches = devices
|
||||
.Where(d => d.Categories.Contains(Category))
|
||||
.OrderBy(d => d.Rectangle.Top)
|
||||
.ThenBy(d => d.Rectangle.Left)
|
||||
.Skip(Skip);
|
||||
if (LimitAmount)
|
||||
matches = matches.Take(Amount);
|
||||
|
||||
foreach (ArtemisDevice artemisDevice in matches)
|
||||
layer.AddLeds(artemisDevice.Leds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new CategoryAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, Category = (int) Category, Skip = Skip};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal CategoryAdaptionHint(CategoryAdaptionHintEntity entity)
|
||||
{
|
||||
Category = (DeviceCategory) entity.Category;
|
||||
Skip = entity.Skip;
|
||||
LimitAmount = entity.LimitAmount;
|
||||
Amount = entity.Amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category of devices LEDs will be applied to
|
||||
/// </summary>
|
||||
public DeviceCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set => SetAndNotify(ref _category, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Category adaption - {nameof(Category)}: {Category}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
IEnumerable<ArtemisDevice> matches = devices
|
||||
.Where(d => d.Categories.Contains(Category))
|
||||
.OrderBy(d => d.Rectangle.Top)
|
||||
.ThenBy(d => d.Rectangle.Left)
|
||||
.Skip(Skip);
|
||||
if (LimitAmount)
|
||||
matches = matches.Take(Amount);
|
||||
|
||||
foreach (ArtemisDevice artemisDevice in matches)
|
||||
layer.AddLeds(artemisDevice.Leds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new CategoryAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, Category = (int) Category, Skip = Skip};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -3,98 +3,97 @@ using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain type of devices
|
||||
/// </summary>
|
||||
public class DeviceAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private int _amount;
|
||||
private RGBDeviceType _deviceType;
|
||||
private bool _limitAmount;
|
||||
private int _skip;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain type of devices
|
||||
/// Creates a new instance of the <see cref="DeviceAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public class DeviceAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
public DeviceAdaptionHint()
|
||||
{
|
||||
private RGBDeviceType _deviceType;
|
||||
private int _skip;
|
||||
private bool _limitAmount;
|
||||
private int _amount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DeviceAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public DeviceAdaptionHint()
|
||||
{
|
||||
}
|
||||
|
||||
internal DeviceAdaptionHint(DeviceAdaptionHintEntity entity)
|
||||
{
|
||||
DeviceType = (RGBDeviceType) entity.DeviceType;
|
||||
Skip = entity.Skip;
|
||||
LimitAmount = entity.LimitAmount;
|
||||
Amount = entity.Amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of devices LEDs will be applied to
|
||||
/// </summary>
|
||||
public RGBDeviceType DeviceType
|
||||
{
|
||||
get => _deviceType;
|
||||
set => SetAndNotify(ref _deviceType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
IEnumerable<ArtemisDevice> matches = devices
|
||||
.Where(d => DeviceType == RGBDeviceType.All || d.DeviceType == DeviceType)
|
||||
.OrderBy(d => d.Rectangle.Top)
|
||||
.ThenBy(d => d.Rectangle.Left)
|
||||
.Skip(Skip);
|
||||
if (LimitAmount)
|
||||
matches = matches.Take(Amount);
|
||||
|
||||
foreach (ArtemisDevice artemisDevice in matches)
|
||||
layer.AddLeds(artemisDevice.Leds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new DeviceAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, DeviceType = (int) DeviceType, Skip = Skip};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Device adaption - {nameof(DeviceType)}: {DeviceType}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal DeviceAdaptionHint(DeviceAdaptionHintEntity entity)
|
||||
{
|
||||
DeviceType = (RGBDeviceType) entity.DeviceType;
|
||||
Skip = entity.Skip;
|
||||
LimitAmount = entity.LimitAmount;
|
||||
Amount = entity.Amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of devices LEDs will be applied to
|
||||
/// </summary>
|
||||
public RGBDeviceType DeviceType
|
||||
{
|
||||
get => _deviceType;
|
||||
set => SetAndNotify(ref _deviceType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
IEnumerable<ArtemisDevice> matches = devices
|
||||
.Where(d => DeviceType == RGBDeviceType.All || d.DeviceType == DeviceType)
|
||||
.OrderBy(d => d.Rectangle.Top)
|
||||
.ThenBy(d => d.Rectangle.Left)
|
||||
.Skip(Skip);
|
||||
if (LimitAmount)
|
||||
matches = matches.Take(Amount);
|
||||
|
||||
foreach (ArtemisDevice artemisDevice in matches)
|
||||
layer.AddLeds(artemisDevice.Leds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new DeviceAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, DeviceType = (int) DeviceType, Skip = Skip};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Device adaption - {nameof(DeviceType)}: {DeviceType}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,23 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an adaption hint that's used to adapt a layer to a set of devices
|
||||
/// </summary>
|
||||
public interface IAdaptionHint
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an adaption hint that's used to adapt a layer to a set of devices
|
||||
/// Applies the adaptive action to the provided layer
|
||||
/// </summary>
|
||||
public interface IAdaptionHint
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the adaptive action to the provided layer
|
||||
/// </summary>
|
||||
/// <param name="layer">The layer to adapt</param>
|
||||
/// <param name="devices">The devices to adapt the layer for</param>
|
||||
void Apply(Layer layer, List<ArtemisDevice> devices);
|
||||
/// <param name="layer">The layer to adapt</param>
|
||||
/// <param name="devices">The devices to adapt the layer for</param>
|
||||
void Apply(Layer layer, List<ArtemisDevice> devices);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an adaption hint entry for this adaption hint used for persistent storage
|
||||
/// </summary>
|
||||
IAdaptionHintEntity GetEntry();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an adaption hint entry for this adaption hint used for persistent storage
|
||||
/// </summary>
|
||||
IAdaptionHintEntity GetEntry();
|
||||
}
|
||||
@ -4,89 +4,88 @@ using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain region of keyboards
|
||||
/// </summary>
|
||||
public class KeyboardSectionAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private static readonly Dictionary<KeyboardSection, List<LedId>> RegionLedIds = new()
|
||||
{
|
||||
{KeyboardSection.MacroKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Programmable1 && l <= LedId.Keyboard_Programmable32).ToList()},
|
||||
{KeyboardSection.LedStrips, Enum.GetValues<LedId>().Where(l => l >= LedId.LedStripe1 && l <= LedId.LedStripe128).ToList()},
|
||||
{KeyboardSection.Extra, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Custom1 && l <= LedId.Keyboard_Custom64).ToList()}
|
||||
};
|
||||
|
||||
private KeyboardSection _section;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="KeyboardSectionAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public KeyboardSectionAdaptionHint()
|
||||
{
|
||||
}
|
||||
|
||||
internal KeyboardSectionAdaptionHint(KeyboardSectionAdaptionHintEntity entity)
|
||||
{
|
||||
Section = (KeyboardSection) entity.Section;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the section this hint will apply LEDs to
|
||||
/// </summary>
|
||||
public KeyboardSection Section
|
||||
{
|
||||
get => _section;
|
||||
set => SetAndNotify(ref _section, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
// Only keyboards should have the LEDs we care about
|
||||
foreach (ArtemisDevice keyboard in devices.Where(d => d.DeviceType == RGBDeviceType.Keyboard))
|
||||
{
|
||||
List<LedId> ledIds = RegionLedIds[Section];
|
||||
layer.AddLeds(keyboard.Leds.Where(l => ledIds.Contains(l.RgbLed.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new KeyboardSectionAdaptionHintEntity {Section = (int) Section};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Keyboard section adaption - {nameof(Section)}: {Section}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a section of LEDs on a keyboard
|
||||
/// </summary>
|
||||
public enum KeyboardSection
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain region of keyboards
|
||||
/// A region containing the macro keys of a keyboard
|
||||
/// </summary>
|
||||
public class KeyboardSectionAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private static readonly Dictionary<KeyboardSection, List<LedId>> RegionLedIds = new()
|
||||
{
|
||||
{KeyboardSection.MacroKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Programmable1 && l <= LedId.Keyboard_Programmable32).ToList()},
|
||||
{KeyboardSection.LedStrips, Enum.GetValues<LedId>().Where(l => l >= LedId.LedStripe1 && l <= LedId.LedStripe128).ToList()},
|
||||
{KeyboardSection.Extra, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Custom1 && l <= LedId.Keyboard_Custom64).ToList()}
|
||||
};
|
||||
|
||||
private KeyboardSection _section;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="KeyboardSectionAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public KeyboardSectionAdaptionHint()
|
||||
{
|
||||
}
|
||||
|
||||
internal KeyboardSectionAdaptionHint(KeyboardSectionAdaptionHintEntity entity)
|
||||
{
|
||||
Section = (KeyboardSection) entity.Section;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the section this hint will apply LEDs to
|
||||
/// </summary>
|
||||
public KeyboardSection Section
|
||||
{
|
||||
get => _section;
|
||||
set => SetAndNotify(ref _section, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
// Only keyboards should have the LEDs we care about
|
||||
foreach (ArtemisDevice keyboard in devices.Where(d => d.DeviceType == RGBDeviceType.Keyboard))
|
||||
{
|
||||
List<LedId> ledIds = RegionLedIds[Section];
|
||||
layer.AddLeds(keyboard.Leds.Where(l => ledIds.Contains(l.RgbLed.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new KeyboardSectionAdaptionHintEntity {Section = (int) Section};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Keyboard section adaption - {nameof(Section)}: {Section}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
MacroKeys,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a section of LEDs on a keyboard
|
||||
/// A region containing the LED strips of a keyboard
|
||||
/// </summary>
|
||||
public enum KeyboardSection
|
||||
{
|
||||
/// <summary>
|
||||
/// A region containing the macro keys of a keyboard
|
||||
/// </summary>
|
||||
MacroKeys,
|
||||
LedStrips,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the LED strips of a keyboard
|
||||
/// </summary>
|
||||
LedStrips,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing extra non-standard LEDs of a keyboard
|
||||
/// </summary>
|
||||
Extra
|
||||
}
|
||||
/// <summary>
|
||||
/// A region containing extra non-standard LEDs of a keyboard
|
||||
/// </summary>
|
||||
Extra
|
||||
}
|
||||
@ -83,14 +83,10 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
||||
{
|
||||
List<SKColor> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
{
|
||||
result = this.Select(c => c.Color).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= timesToRepeat; i++)
|
||||
result.AddRange(this.Select(c => c.Color));
|
||||
}
|
||||
|
||||
if (seamless && !IsSeamless())
|
||||
result.Add(result[0]);
|
||||
@ -413,8 +409,10 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (!Equals(this[i], other[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,69 +1,68 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A color with a position, usually contained in a <see cref="ColorGradient" />
|
||||
/// </summary>
|
||||
public class ColorGradientStop : CorePropertyChanged
|
||||
{
|
||||
private SKColor _color;
|
||||
private float _position;
|
||||
|
||||
/// <summary>
|
||||
/// A color with a position, usually contained in a <see cref="ColorGradient" />
|
||||
/// Creates a new instance of the <see cref="ColorGradientStop" /> class
|
||||
/// </summary>
|
||||
public class ColorGradientStop : CorePropertyChanged
|
||||
public ColorGradientStop(SKColor color, float position)
|
||||
{
|
||||
#region Equality members
|
||||
|
||||
/// <inheritdoc cref="object.Equals(object)" />
|
||||
protected bool Equals(ColorGradientStop other)
|
||||
{
|
||||
return _color.Equals(other._color) && _position.Equals(other._position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
return Equals((ColorGradientStop) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_color, _position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private SKColor _color;
|
||||
private float _position;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorGradientStop" /> class
|
||||
/// </summary>
|
||||
public ColorGradientStop(SKColor color, float position)
|
||||
{
|
||||
Color = color;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the stop
|
||||
/// </summary>
|
||||
public SKColor Color
|
||||
{
|
||||
get => _color;
|
||||
set => SetAndNotify(ref _color, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the stop
|
||||
/// </summary>
|
||||
public float Position
|
||||
{
|
||||
get => _position;
|
||||
set => SetAndNotify(ref _position, value);
|
||||
}
|
||||
Color = color;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the stop
|
||||
/// </summary>
|
||||
public SKColor Color
|
||||
{
|
||||
get => _color;
|
||||
set => SetAndNotify(ref _color, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the stop
|
||||
/// </summary>
|
||||
public float Position
|
||||
{
|
||||
get => _position;
|
||||
set => SetAndNotify(ref _position, value);
|
||||
}
|
||||
|
||||
#region Equality members
|
||||
|
||||
/// <inheritdoc cref="object.Equals(object)" />
|
||||
protected bool Equals(ColorGradientStop other)
|
||||
{
|
||||
return _color.Equals(other._color) && _position.Equals(other._position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
return Equals((ColorGradientStop) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_color, _position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -2,89 +2,84 @@
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition that is always true.
|
||||
/// </summary>
|
||||
public class AlwaysOnCondition : ICondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition that is always true.
|
||||
/// Creates a new instance of the <see cref="AlwaysOnCondition" /> class.
|
||||
/// </summary>
|
||||
public class AlwaysOnCondition : ICondition
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public AlwaysOnCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AlwaysOnCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public AlwaysOnCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = new AlwaysOnConditionEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AlwaysOnCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="alwaysOnConditionEntity">The entity used to store this condition.</param>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public AlwaysOnCondition(AlwaysOnConditionEntity alwaysOnConditionEntity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = alwaysOnConditionEntity;
|
||||
}
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
ProfileElement = profileElement;
|
||||
Entity = new AlwaysOnConditionEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AlwaysOnCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="alwaysOnConditionEntity">The entity used to store this condition.</param>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public AlwaysOnCondition(AlwaysOnConditionEntity alwaysOnConditionEntity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = alwaysOnConditionEntity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -14,15 +14,15 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
{
|
||||
private readonly string _displayName;
|
||||
private readonly EventConditionEntity _entity;
|
||||
private IEventConditionNode _startNode;
|
||||
private DataModelPath? _eventPath;
|
||||
private NodeScript<bool> _script;
|
||||
private bool _wasMet;
|
||||
private DateTime _lastProcessedTrigger;
|
||||
private object? _lastProcessedValue;
|
||||
private EventOverlapMode _overlapMode;
|
||||
private EventTriggerMode _triggerMode;
|
||||
private NodeScript<bool> _script;
|
||||
private IEventConditionNode _startNode;
|
||||
private EventToggleOffMode _toggleOffMode;
|
||||
private EventTriggerMode _triggerMode;
|
||||
private bool _wasMet;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EventCondition" /> class
|
||||
@ -87,7 +87,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle"/>.
|
||||
/// Gets or sets the mode for render elements when toggling off the event when using
|
||||
/// <see cref="EventTriggerMode.Toggle" />.
|
||||
/// </summary>
|
||||
public EventToggleOffMode ToggleOffMode
|
||||
{
|
||||
@ -119,7 +120,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
_startNode = eventNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
eventNode = node;
|
||||
}
|
||||
|
||||
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
|
||||
eventNode.CreatePins(dataModelEvent);
|
||||
@ -136,13 +139,25 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
ReplaceStartNode(valueChangedNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueChangedNode = node;
|
||||
}
|
||||
|
||||
valueChangedNode.UpdateOutputPins(EventPath);
|
||||
}
|
||||
|
||||
Script.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start node of the event script, if any
|
||||
/// </summary>
|
||||
/// <returns>The start node of the event script, if any.</returns>
|
||||
public INode GetStartNode()
|
||||
{
|
||||
return _startNode;
|
||||
}
|
||||
|
||||
private void ReplaceStartNode(IEventConditionNode newStartNode)
|
||||
{
|
||||
if (Script.Nodes.Contains(_startNode))
|
||||
@ -153,15 +168,6 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
Script.AddNode(_startNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start node of the event script, if any
|
||||
/// </summary>
|
||||
/// <returns>The start node of the event script, if any.</returns>
|
||||
public INode GetStartNode()
|
||||
{
|
||||
return _startNode;
|
||||
}
|
||||
|
||||
private bool Evaluate()
|
||||
{
|
||||
if (EventPath == null)
|
||||
@ -271,7 +277,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
|
||||
Script = _entity.Script != null
|
||||
? new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile)
|
||||
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
|
||||
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
|
||||
UpdateEventNode();
|
||||
}
|
||||
|
||||
@ -356,7 +362,8 @@ public enum EventOverlapMode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle"/>.
|
||||
/// Represents a mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle" />
|
||||
/// .
|
||||
/// </summary>
|
||||
public enum EventToggleOffMode
|
||||
{
|
||||
|
||||
@ -30,12 +30,12 @@ public interface ICondition : IDisposable, IStorageModel
|
||||
void Update();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timeline according to the provided <paramref name="deltaTime" /> as the display condition sees fit.
|
||||
/// Updates the timeline according to the provided <paramref name="deltaTime" /> as the display condition sees fit.
|
||||
/// </summary>
|
||||
void UpdateTimeline(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the timeline to the provided <paramref name="position"/> as the display condition sees fit.
|
||||
/// Overrides the timeline to the provided <paramref name="position" /> as the display condition sees fit.
|
||||
/// </summary>
|
||||
void OverrideTimeline(TimeSpan position);
|
||||
}
|
||||
|
||||
@ -2,89 +2,84 @@
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition that plays once when its script evaluates to <see langword="true" />.
|
||||
/// </summary>
|
||||
public class PlayOnceCondition : ICondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition that plays once when its script evaluates to <see langword="true"/>.
|
||||
/// Creates a new instance of the <see cref="PlayOnceCondition" /> class.
|
||||
/// </summary>
|
||||
public class PlayOnceCondition : ICondition
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public PlayOnceCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PlayOnceCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public PlayOnceCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = new PlayOnceConditionEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PlayOnceCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity used to store this condition.</param>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public PlayOnceCondition(PlayOnceConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
ProfileElement = profileElement;
|
||||
Entity = new PlayOnceConditionEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PlayOnceCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity used to store this condition.</param>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public PlayOnceCondition(PlayOnceConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -3,191 +3,192 @@ using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition that is based on a data model value
|
||||
/// </summary>
|
||||
public class StaticCondition : CorePropertyChanged, INodeScriptCondition
|
||||
{
|
||||
private readonly string _displayName;
|
||||
private readonly StaticConditionEntity _entity;
|
||||
private StaticPlayMode _playMode;
|
||||
private StaticStopMode _stopMode;
|
||||
private bool _wasMet;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="StaticCondition" /> class
|
||||
/// </summary>
|
||||
public StaticCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
_entity = new StaticConditionEntity();
|
||||
_displayName = profileElement.GetType().Name;
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", profileElement.Profile);
|
||||
}
|
||||
|
||||
internal StaticCondition(StaticConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
_entity = entity;
|
||||
_displayName = profileElement.GetType().Name;
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Script = null!;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script that drives the static condition
|
||||
/// </summary>
|
||||
public NodeScript<bool> Script { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
|
||||
/// </summary>
|
||||
public StaticPlayMode PlayMode
|
||||
{
|
||||
get => _playMode;
|
||||
set => SetAndNotify(ref _playMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public StaticStopMode StopMode
|
||||
{
|
||||
get => _stopMode;
|
||||
set => SetAndNotify(ref _stopMode, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity => _entity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
_wasMet = IsMet;
|
||||
|
||||
// No need to run the script if the parent isn't met anyway
|
||||
bool parentConditionMet = ProfileElement.Parent is not RenderProfileElement renderProfileElement || renderProfileElement.DisplayConditionMet;
|
||||
if (!parentConditionMet)
|
||||
{
|
||||
IsMet = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Script.ExitNodeConnected)
|
||||
{
|
||||
IsMet = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Script.Run();
|
||||
IsMet = Script.Result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
if (IsMet && !_wasMet && ProfileElement.Timeline.IsFinished)
|
||||
ProfileElement.Timeline.JumpToStart();
|
||||
else if (!IsMet && _wasMet && StopMode == StaticStopMode.SkipToEnd)
|
||||
ProfileElement.Timeline.JumpToEndSegment();
|
||||
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), PlayMode == StaticPlayMode.Repeat && IsMet);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, PlayMode == StaticPlayMode.Repeat && position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Script.Dispose();
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
PlayMode = (StaticPlayMode) _entity.PlayMode;
|
||||
StopMode = (StaticStopMode) _entity.StopMode;
|
||||
|
||||
Script = _entity.Script != null
|
||||
? new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", _entity.Script, ProfileElement.Profile)
|
||||
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", ProfileElement.Profile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
_entity.PlayMode = (int) PlayMode;
|
||||
_entity.StopMode = (int) StopMode;
|
||||
|
||||
// If the exit node isn't connected and there is only the exit node, don't save the script
|
||||
if (!Script.ExitNodeConnected && Script.Nodes.Count() == 1)
|
||||
{
|
||||
_entity.Script = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Script.Save();
|
||||
_entity.Script = Script.Entity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INodeScript? NodeScript => Script;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadNodeScript()
|
||||
{
|
||||
Script.Load();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to start their timeline when display conditions are met
|
||||
/// </summary>
|
||||
public enum StaticPlayMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition that is based on a data model value
|
||||
/// Continue repeating the main segment of the timeline while the condition is met
|
||||
/// </summary>
|
||||
public class StaticCondition : CorePropertyChanged, INodeScriptCondition
|
||||
{
|
||||
private readonly string _displayName;
|
||||
private readonly StaticConditionEntity _entity;
|
||||
private StaticPlayMode _playMode;
|
||||
private StaticStopMode _stopMode;
|
||||
private bool _wasMet;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="StaticCondition" /> class
|
||||
/// </summary>
|
||||
public StaticCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
_entity = new StaticConditionEntity();
|
||||
_displayName = profileElement.GetType().Name;
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", profileElement.Profile);
|
||||
}
|
||||
|
||||
internal StaticCondition(StaticConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
_entity = entity;
|
||||
_displayName = profileElement.GetType().Name;
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Script = null!;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script that drives the static condition
|
||||
/// </summary>
|
||||
public NodeScript<bool> Script { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity => _entity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
|
||||
/// </summary>
|
||||
public StaticPlayMode PlayMode
|
||||
{
|
||||
get => _playMode;
|
||||
set => SetAndNotify(ref _playMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public StaticStopMode StopMode
|
||||
{
|
||||
get => _stopMode;
|
||||
set => SetAndNotify(ref _stopMode, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
_wasMet = IsMet;
|
||||
|
||||
// No need to run the script if the parent isn't met anyway
|
||||
bool parentConditionMet = ProfileElement.Parent is not RenderProfileElement renderProfileElement || renderProfileElement.DisplayConditionMet;
|
||||
if (!parentConditionMet)
|
||||
{
|
||||
IsMet = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Script.ExitNodeConnected)
|
||||
IsMet = true;
|
||||
else
|
||||
{
|
||||
Script.Run();
|
||||
IsMet = Script.Result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
if (IsMet && !_wasMet && ProfileElement.Timeline.IsFinished)
|
||||
ProfileElement.Timeline.JumpToStart();
|
||||
else if (!IsMet && _wasMet && StopMode == StaticStopMode.SkipToEnd)
|
||||
ProfileElement.Timeline.JumpToEndSegment();
|
||||
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), PlayMode == StaticPlayMode.Repeat && IsMet);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, PlayMode == StaticPlayMode.Repeat && position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Script.Dispose();
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
PlayMode = (StaticPlayMode) _entity.PlayMode;
|
||||
StopMode = (StaticStopMode) _entity.StopMode;
|
||||
|
||||
Script = _entity.Script != null
|
||||
? new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", _entity.Script, ProfileElement.Profile)
|
||||
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", ProfileElement.Profile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
_entity.PlayMode = (int) PlayMode;
|
||||
_entity.StopMode = (int) StopMode;
|
||||
|
||||
// If the exit node isn't connected and there is only the exit node, don't save the script
|
||||
if (!Script.ExitNodeConnected && Script.Nodes.Count() == 1)
|
||||
{
|
||||
_entity.Script = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Script.Save();
|
||||
_entity.Script = Script.Entity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INodeScript? NodeScript => Script;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadNodeScript()
|
||||
{
|
||||
Script.Load();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Repeat,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to start their timeline when display conditions are met
|
||||
/// Only play the timeline once when the condition is met
|
||||
/// </summary>
|
||||
public enum StaticPlayMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Continue repeating the main segment of the timeline while the condition is met
|
||||
/// </summary>
|
||||
Repeat,
|
||||
Once
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only play the timeline once when the condition is met
|
||||
/// </summary>
|
||||
Once
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public enum StaticStopMode
|
||||
{
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, finish the the current run of the main timeline
|
||||
/// </summary>
|
||||
Finish,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
|
||||
/// When conditions are no longer met, skip to the end segment of the timeline
|
||||
/// </summary>
|
||||
public enum StaticStopMode
|
||||
{
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, finish the the current run of the main timeline
|
||||
/// </summary>
|
||||
Finish,
|
||||
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, skip to the end segment of the timeline
|
||||
/// </summary>
|
||||
SkipToEnd
|
||||
}
|
||||
SkipToEnd
|
||||
}
|
||||
@ -4,242 +4,243 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class DataBinding<TLayerProperty> : IDataBinding
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class DataBinding<TLayerProperty> : IDataBinding
|
||||
private readonly List<IDataBindingProperty> _properties = new();
|
||||
private bool _disposed;
|
||||
private bool _isEnabled;
|
||||
private DataBindingNodeScript<TLayerProperty> _script;
|
||||
|
||||
internal DataBinding(LayerProperty<TLayerProperty> layerProperty)
|
||||
{
|
||||
private readonly List<IDataBindingProperty> _properties = new();
|
||||
private bool _disposed;
|
||||
private bool _isEnabled;
|
||||
private DataBindingNodeScript<TLayerProperty> _script;
|
||||
LayerProperty = layerProperty;
|
||||
|
||||
internal DataBinding(LayerProperty<TLayerProperty> layerProperty)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
Entity = new DataBindingEntity();
|
||||
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||
|
||||
Entity = new DataBindingEntity();
|
||||
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
|
||||
Entity = entity;
|
||||
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||
|
||||
// Load will add children so be initialized before that
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer property this data binding targets
|
||||
/// </summary>
|
||||
public LayerProperty<TLayerProperty> LayerProperty { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public INodeScript Script => _script;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding entity this data binding uses for persistent storage
|
||||
/// </summary>
|
||||
public DataBindingEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the pending values of this data binding
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
if (!IsEnabled)
|
||||
return;
|
||||
|
||||
// TODO: Update the 'base value' node
|
||||
|
||||
Script.Run();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a data binding property so that is available to the data binding system
|
||||
/// </summary>
|
||||
/// <typeparam name="TProperty">The type of the layer property</typeparam>
|
||||
/// <param name="getter">The function to call to get the value of the property</param>
|
||||
/// <param name="setter">The action to call to set the value of the property</param>
|
||||
/// <param name="displayName">The display name of the data binding property</param>
|
||||
public DataBindingProperty<TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
if (Properties.Any(d => d.DisplayName == displayName))
|
||||
throw new ArtemisCoreException($"A data binding property named '{displayName}' is already registered.");
|
||||
|
||||
DataBindingProperty<TProperty> property = new(getter, setter, displayName);
|
||||
_properties.Add(property);
|
||||
|
||||
OnDataBindingPropertyRegistered();
|
||||
return property;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all data binding properties so they are no longer available to the data binding system
|
||||
/// </summary>
|
||||
public void ClearDataBindingProperties()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
_properties.Clear();
|
||||
OnDataBindingPropertiesCleared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
_isEnabled = false;
|
||||
|
||||
Script.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingPropertyRegistered()
|
||||
{
|
||||
DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingPropertiesCleared" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingPropertiesCleared()
|
||||
{
|
||||
DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingEnabled" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingEnabled(DataBindingEventArgs e)
|
||||
{
|
||||
DataBindingEnabled?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingDisabled" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingDisabled(DataBindingEventArgs e)
|
||||
{
|
||||
DataBindingDisabled?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private string GetScriptName()
|
||||
{
|
||||
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayerProperty BaseLayerProperty => LayerProperty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
_isEnabled = value;
|
||||
|
||||
if (_isEnabled)
|
||||
OnDataBindingEnabled(new DataBindingEventArgs(this));
|
||||
else
|
||||
OnDataBindingDisabled(new DataBindingEventArgs(this));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyCollection<IDataBindingProperty> Properties => _properties.AsReadOnly();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
if (!IsEnabled)
|
||||
return;
|
||||
|
||||
_script.DataBindingExitNode.ApplyToDataBinding();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
IsEnabled = Entity.IsEnabled;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadNodeScript()
|
||||
{
|
||||
_script.Dispose();
|
||||
_script = Entity.NodeScript != null
|
||||
? new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile)
|
||||
: new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
Entity.IsEnabled = IsEnabled;
|
||||
if (_script.ExitNodeConnected || _script.Nodes.Count() > 1)
|
||||
{
|
||||
_script.Save();
|
||||
Entity.NodeScript = _script.Entity;
|
||||
}
|
||||
else
|
||||
Entity.NodeScript = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
Save();
|
||||
}
|
||||
|
||||
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
|
||||
Entity = entity;
|
||||
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||
|
||||
// Load will add children so be initialized before that
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer property this data binding targets
|
||||
/// </summary>
|
||||
public LayerProperty<TLayerProperty> LayerProperty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding entity this data binding uses for persistent storage
|
||||
/// </summary>
|
||||
public DataBindingEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the pending values of this data binding
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
if (!IsEnabled)
|
||||
return;
|
||||
|
||||
// TODO: Update the 'base value' node
|
||||
|
||||
Script.Run();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a data binding property so that is available to the data binding system
|
||||
/// </summary>
|
||||
/// <typeparam name="TProperty">The type of the layer property</typeparam>
|
||||
/// <param name="getter">The function to call to get the value of the property</param>
|
||||
/// <param name="setter">The action to call to set the value of the property</param>
|
||||
/// <param name="displayName">The display name of the data binding property</param>
|
||||
public DataBindingProperty<TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
if (Properties.Any(d => d.DisplayName == displayName))
|
||||
throw new ArtemisCoreException($"A data binding property named '{displayName}' is already registered.");
|
||||
|
||||
DataBindingProperty<TProperty> property = new(getter, setter, displayName);
|
||||
_properties.Add(property);
|
||||
|
||||
OnDataBindingPropertyRegistered();
|
||||
return property;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all data binding properties so they are no longer available to the data binding system
|
||||
/// </summary>
|
||||
public void ClearDataBindingProperties()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
_properties.Clear();
|
||||
OnDataBindingPropertiesCleared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
_isEnabled = false;
|
||||
|
||||
Script.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingPropertyRegistered()
|
||||
{
|
||||
DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingPropertiesCleared" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingPropertiesCleared()
|
||||
{
|
||||
DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingEnabled" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingEnabled(DataBindingEventArgs e)
|
||||
{
|
||||
DataBindingEnabled?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingDisabled" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingDisabled(DataBindingEventArgs e)
|
||||
{
|
||||
DataBindingDisabled?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private string GetScriptName()
|
||||
{
|
||||
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INodeScript Script => _script;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayerProperty BaseLayerProperty => LayerProperty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
_isEnabled = value;
|
||||
|
||||
if (_isEnabled)
|
||||
OnDataBindingEnabled(new DataBindingEventArgs(this));
|
||||
else
|
||||
OnDataBindingDisabled(new DataBindingEventArgs(this));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyCollection<IDataBindingProperty> Properties => _properties.AsReadOnly();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
if (!IsEnabled)
|
||||
return;
|
||||
|
||||
_script.DataBindingExitNode.ApplyToDataBinding();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
IsEnabled = Entity.IsEnabled;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadNodeScript()
|
||||
{
|
||||
_script.Dispose();
|
||||
_script = Entity.NodeScript != null
|
||||
? new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile)
|
||||
: new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
Entity.IsEnabled = IsEnabled;
|
||||
if (_script.ExitNodeConnected || _script.Nodes.Count() > 1)
|
||||
{
|
||||
_script.Save();
|
||||
Entity.NodeScript = _script.Entity;
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.NodeScript = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,63 +1,62 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class DataBindingProperty<TProperty> : IDataBindingProperty
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class DataBindingProperty<TProperty> : IDataBindingProperty
|
||||
internal DataBindingProperty(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
|
||||
{
|
||||
internal DataBindingProperty(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
|
||||
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
|
||||
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
|
||||
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function to call to get the value of the property
|
||||
/// </summary>
|
||||
public Func<TProperty> Getter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the action to call to set the value of the property
|
||||
/// </summary>
|
||||
public Action<TProperty?> Setter { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type ValueType => typeof(TProperty);
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? GetValue()
|
||||
{
|
||||
return Getter();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(object? value)
|
||||
{
|
||||
// Numeric has a bunch of conversion, this seems the cheapest way to use them :)
|
||||
switch (value)
|
||||
{
|
||||
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
|
||||
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
|
||||
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function to call to get the value of the property
|
||||
/// </summary>
|
||||
public Func<TProperty> Getter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the action to call to set the value of the property
|
||||
/// </summary>
|
||||
public Action<TProperty?> Setter { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type ValueType => typeof(TProperty);
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? GetValue()
|
||||
{
|
||||
return Getter();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(object? value)
|
||||
{
|
||||
// Numeric has a bunch of conversion, this seems the cheapest way to use them :)
|
||||
switch (value)
|
||||
{
|
||||
case TProperty match:
|
||||
Setter(match);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<float> floatSetter:
|
||||
floatSetter(numeric);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<int> intSetter:
|
||||
intSetter(numeric);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<double> doubleSetter:
|
||||
doubleSetter(numeric);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<byte> byteSetter:
|
||||
byteSetter(numeric);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value));
|
||||
}
|
||||
case TProperty match:
|
||||
Setter(match);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<float> floatSetter:
|
||||
floatSetter(numeric);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<int> intSetter:
|
||||
intSetter(numeric);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<double> doubleSetter:
|
||||
doubleSetter(numeric);
|
||||
break;
|
||||
case Numeric numeric when Setter is Action<byte> byteSetter:
|
||||
byteSetter(numeric);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,62 +2,61 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
|
||||
/// <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public interface IDataBinding : IStorageModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
|
||||
/// <see cref="DataModel" />
|
||||
/// Gets the layer property the data binding is applied to
|
||||
/// </summary>
|
||||
public interface IDataBinding : IStorageModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the layer property the data binding is applied to
|
||||
/// </summary>
|
||||
ILayerProperty BaseLayerProperty { get; }
|
||||
ILayerProperty BaseLayerProperty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script used to populate the data binding
|
||||
/// </summary>
|
||||
INodeScript Script { get; }
|
||||
/// <summary>
|
||||
/// Gets the script used to populate the data binding
|
||||
/// </summary>
|
||||
INodeScript Script { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of sub-properties this data binding applies to
|
||||
/// </summary>
|
||||
ReadOnlyCollection<IDataBindingProperty> Properties { get; }
|
||||
/// <summary>
|
||||
/// Gets a list of sub-properties this data binding applies to
|
||||
/// </summary>
|
||||
ReadOnlyCollection<IDataBindingProperty> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the data binding is enabled or not
|
||||
/// </summary>
|
||||
bool IsEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the data binding is enabled or not
|
||||
/// </summary>
|
||||
bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the pending value of the data binding to the property
|
||||
/// </summary>
|
||||
void Apply();
|
||||
/// <summary>
|
||||
/// Applies the pending value of the data binding to the property
|
||||
/// </summary>
|
||||
void Apply();
|
||||
|
||||
/// <summary>
|
||||
/// If the data binding is enabled, loads the node script for that data binding
|
||||
/// </summary>
|
||||
void LoadNodeScript();
|
||||
/// <summary>
|
||||
/// If the data binding is enabled, loads the node script for that data binding
|
||||
/// </summary>
|
||||
void LoadNodeScript();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a data binding property has been added
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
|
||||
/// <summary>
|
||||
/// Occurs when a data binding property has been added
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when all data binding properties have been removed
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
|
||||
/// <summary>
|
||||
/// Occurs when all data binding properties have been removed
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a data binding has been enabled
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
|
||||
/// <summary>
|
||||
/// Occurs when a data binding has been enabled
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a data binding has been disabled
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
|
||||
}
|
||||
/// <summary>
|
||||
/// Occurs when a data binding has been disabled
|
||||
/// </summary>
|
||||
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
|
||||
}
|
||||
@ -1,32 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data binding registration
|
||||
/// </summary>
|
||||
public interface IDataBindingProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data binding registration
|
||||
/// Gets or sets the display name of the data binding registration
|
||||
/// </summary>
|
||||
public interface IDataBindingProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the display name of the data binding registration
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the value this data binding registration points to
|
||||
/// </summary>
|
||||
Type ValueType { get; }
|
||||
/// <summary>
|
||||
/// Gets the type of the value this data binding registration points to
|
||||
/// </summary>
|
||||
Type ValueType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the property this registration points to
|
||||
/// </summary>
|
||||
/// <returns>A value matching the type of <see cref="ValueType" /></returns>
|
||||
object? GetValue();
|
||||
/// <summary>
|
||||
/// Gets the value of the property this registration points to
|
||||
/// </summary>
|
||||
/// <returns>A value matching the type of <see cref="ValueType" /></returns>
|
||||
object? GetValue();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the property this registration points to
|
||||
/// </summary>
|
||||
/// <param name="value">A value matching the type of <see cref="ValueType" /></param>
|
||||
void SetValue(object? value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the value of the property this registration points to
|
||||
/// </summary>
|
||||
/// <param name="value">A value matching the type of <see cref="ValueType" /></param>
|
||||
void SetValue(object? value);
|
||||
}
|
||||
@ -3,249 +3,244 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data model event with event arguments of type <typeparamref name="T" />
|
||||
/// </summary>
|
||||
public class DataModelEvent<T> : IDataModelEvent where T : DataModelEventArgs
|
||||
{
|
||||
private bool _trackHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data model event with event arguments of type <typeparamref name="T" />
|
||||
/// Creates a new instance of the <see cref="DataModelEvent{T}" /> class with history tracking disabled
|
||||
/// </summary>
|
||||
public class DataModelEvent<T> : IDataModelEvent where T : DataModelEventArgs
|
||||
public DataModelEvent()
|
||||
{
|
||||
private bool _trackHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelEvent{T}" /> class with history tracking disabled
|
||||
/// </summary>
|
||||
public DataModelEvent()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelEvent{T}" />
|
||||
/// </summary>
|
||||
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
|
||||
public DataModelEvent(bool trackHistory)
|
||||
{
|
||||
_trackHistory = trackHistory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")]
|
||||
public DateTime LastTrigger { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")]
|
||||
public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event arguments of the last time the event was triggered
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public T? LastEventArguments { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
|
||||
public int TriggerCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a queue of the last 20 event arguments
|
||||
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public Queue<T> EventArgumentsHistory { get; } = new(20);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the event with the given <paramref name="eventArgs" />
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">The event argument to pass to the event</param>
|
||||
public void Trigger(T eventArgs)
|
||||
{
|
||||
if (eventArgs == null) throw new ArgumentNullException(nameof(eventArgs));
|
||||
eventArgs.TriggerTime = DateTime.Now;
|
||||
|
||||
LastEventArguments = eventArgs;
|
||||
LastTrigger = DateTime.Now;
|
||||
TriggerCount++;
|
||||
|
||||
if (TrackHistory)
|
||||
{
|
||||
lock (EventArgumentsHistory)
|
||||
{
|
||||
if (EventArgumentsHistory.Count == 20)
|
||||
EventArgumentsHistory.Dequeue();
|
||||
EventArgumentsHistory.Enqueue(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
OnEventTriggered();
|
||||
}
|
||||
|
||||
internal virtual void OnEventTriggered()
|
||||
{
|
||||
EventTriggered?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public Type ArgumentsType => typeof(T);
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public string TriggerPastParticiple => "triggered";
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public bool TrackHistory
|
||||
{
|
||||
get => _trackHistory;
|
||||
set
|
||||
{
|
||||
EventArgumentsHistory.Clear();
|
||||
_trackHistory = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.Cast<DataModelEventArgs>().ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? EventTriggered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
TriggerCount = 0;
|
||||
EventArgumentsHistory.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data model event without event arguments
|
||||
/// Creates a new instance of the <see cref="DataModelEvent{T}" />
|
||||
/// </summary>
|
||||
public class DataModelEvent : IDataModelEvent
|
||||
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
|
||||
public DataModelEvent(bool trackHistory)
|
||||
{
|
||||
private bool _trackHistory;
|
||||
_trackHistory = trackHistory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelEvent" /> class with history tracking disabled
|
||||
/// </summary>
|
||||
public DataModelEvent()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the event arguments of the last time the event was triggered
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public T? LastEventArguments { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelEvent" />
|
||||
/// </summary>
|
||||
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
|
||||
public DataModelEvent(bool trackHistory)
|
||||
{
|
||||
_trackHistory = trackHistory;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a queue of the last 20 event arguments
|
||||
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public Queue<T> EventArgumentsHistory { get; } = new(20);
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")]
|
||||
public DateTime LastTrigger { get; private set; }
|
||||
/// <summary>
|
||||
/// Trigger the event with the given <paramref name="eventArgs" />
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">The event argument to pass to the event</param>
|
||||
public void Trigger(T eventArgs)
|
||||
{
|
||||
if (eventArgs == null) throw new ArgumentNullException(nameof(eventArgs));
|
||||
eventArgs.TriggerTime = DateTime.Now;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")]
|
||||
public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
|
||||
LastEventArguments = eventArgs;
|
||||
LastTrigger = DateTime.Now;
|
||||
TriggerCount++;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event arguments of the last time the event was triggered
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public DataModelEventArgs? LastTriggerArguments { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
|
||||
public int TriggerCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a queue of the last 20 event arguments
|
||||
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public Queue<DataModelEventArgs> EventArgumentsHistory { get; } = new(20);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the event
|
||||
/// </summary>
|
||||
public void Trigger()
|
||||
{
|
||||
DataModelEventArgs eventArgs = new() {TriggerTime = DateTime.Now};
|
||||
|
||||
LastTriggerArguments = eventArgs;
|
||||
LastTrigger = DateTime.Now;
|
||||
TriggerCount++;
|
||||
|
||||
if (TrackHistory)
|
||||
if (TrackHistory)
|
||||
lock (EventArgumentsHistory)
|
||||
{
|
||||
lock (EventArgumentsHistory)
|
||||
{
|
||||
if (EventArgumentsHistory.Count == 20)
|
||||
EventArgumentsHistory.Dequeue();
|
||||
EventArgumentsHistory.Enqueue(eventArgs);
|
||||
}
|
||||
if (EventArgumentsHistory.Count == 20)
|
||||
EventArgumentsHistory.Dequeue();
|
||||
EventArgumentsHistory.Enqueue(eventArgs);
|
||||
}
|
||||
|
||||
OnEventTriggered();
|
||||
}
|
||||
OnEventTriggered();
|
||||
}
|
||||
|
||||
internal virtual void OnEventTriggered()
|
||||
internal virtual void OnEventTriggered()
|
||||
{
|
||||
EventTriggered?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")]
|
||||
public DateTime LastTrigger { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")]
|
||||
public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
|
||||
public int TriggerCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public Type ArgumentsType => typeof(T);
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public string TriggerPastParticiple => "triggered";
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public bool TrackHistory
|
||||
{
|
||||
get => _trackHistory;
|
||||
set
|
||||
{
|
||||
EventTriggered?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public Type ArgumentsType => typeof(DataModelEventArgs);
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public string TriggerPastParticiple => "triggered";
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public bool TrackHistory
|
||||
{
|
||||
get => _trackHistory;
|
||||
set
|
||||
{
|
||||
EventArgumentsHistory.Clear();
|
||||
_trackHistory = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public DataModelEventArgs? LastEventArgumentsUntyped => LastTriggerArguments;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? EventTriggered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
TriggerCount = 0;
|
||||
EventArgumentsHistory.Clear();
|
||||
_trackHistory = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.Cast<DataModelEventArgs>().ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? EventTriggered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
TriggerCount = 0;
|
||||
EventArgumentsHistory.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data model event without event arguments
|
||||
/// </summary>
|
||||
public class DataModelEvent : IDataModelEvent
|
||||
{
|
||||
private bool _trackHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelEvent" /> class with history tracking disabled
|
||||
/// </summary>
|
||||
public DataModelEvent()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelEvent" />
|
||||
/// </summary>
|
||||
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
|
||||
public DataModelEvent(bool trackHistory)
|
||||
{
|
||||
_trackHistory = trackHistory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event arguments of the last time the event was triggered
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public DataModelEventArgs? LastTriggerArguments { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a queue of the last 20 event arguments
|
||||
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||
/// </summary>
|
||||
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||
public Queue<DataModelEventArgs> EventArgumentsHistory { get; } = new(20);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the event
|
||||
/// </summary>
|
||||
public void Trigger()
|
||||
{
|
||||
DataModelEventArgs eventArgs = new() {TriggerTime = DateTime.Now};
|
||||
|
||||
LastTriggerArguments = eventArgs;
|
||||
LastTrigger = DateTime.Now;
|
||||
TriggerCount++;
|
||||
|
||||
if (TrackHistory)
|
||||
lock (EventArgumentsHistory)
|
||||
{
|
||||
if (EventArgumentsHistory.Count == 20)
|
||||
EventArgumentsHistory.Dequeue();
|
||||
EventArgumentsHistory.Enqueue(eventArgs);
|
||||
}
|
||||
|
||||
OnEventTriggered();
|
||||
}
|
||||
|
||||
internal virtual void OnEventTriggered()
|
||||
{
|
||||
EventTriggered?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")]
|
||||
public DateTime LastTrigger { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")]
|
||||
public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
|
||||
public int TriggerCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public Type ArgumentsType => typeof(DataModelEventArgs);
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public string TriggerPastParticiple => "triggered";
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public bool TrackHistory
|
||||
{
|
||||
get => _trackHistory;
|
||||
set
|
||||
{
|
||||
EventArgumentsHistory.Clear();
|
||||
_trackHistory = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public DataModelEventArgs? LastEventArgumentsUntyped => LastTriggerArguments;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataModelIgnore]
|
||||
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? EventTriggered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
TriggerCount = 0;
|
||||
EventArgumentsHistory.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,16 @@
|
||||
using System;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base class for data model events that contain event data
|
||||
/// </summary>
|
||||
public class DataModelEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base class for data model events that contain event data
|
||||
/// Gets the time at which the event with these arguments was triggered
|
||||
/// </summary>
|
||||
public class DataModelEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the time at which the event with these arguments was triggered
|
||||
/// </summary>
|
||||
[DataModelIgnore]
|
||||
public DateTime TriggerTime { get; internal set; }
|
||||
}
|
||||
[DataModelIgnore]
|
||||
public DateTime TriggerTime { get; internal set; }
|
||||
}
|
||||
@ -6,388 +6,388 @@ using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path that points to a property in data model
|
||||
/// </summary>
|
||||
public class DataModelPath : IStorageModel, IDisposable
|
||||
{
|
||||
private readonly LinkedList<DataModelPathSegment> _segments;
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path that points to a property in data model
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class pointing directly to the target
|
||||
/// </summary>
|
||||
public class DataModelPath : IStorageModel, IDisposable
|
||||
/// <param name="target">The target at which this path starts</param>
|
||||
public DataModelPath(DataModel target)
|
||||
{
|
||||
private readonly LinkedList<DataModelPathSegment> _segments;
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
private bool _disposed;
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Path = "";
|
||||
Entity = new DataModelPathEntity();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class pointing directly to the target
|
||||
/// </summary>
|
||||
/// <param name="target">The target at which this path starts</param>
|
||||
public DataModelPath(DataModel target)
|
||||
{
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Path = "";
|
||||
Entity = new DataModelPathEntity();
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class pointing to the provided path
|
||||
/// </summary>
|
||||
/// <param name="target">The target at which this path starts</param>
|
||||
/// <param name="path">A point-separated path</param>
|
||||
public DataModelPath(DataModel target, string path)
|
||||
{
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
Entity = new DataModelPathEntity();
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class based on an existing path
|
||||
/// </summary>
|
||||
/// <param name="dataModelPath">The path to base the new instance on</param>
|
||||
public DataModelPath(DataModelPath dataModelPath)
|
||||
{
|
||||
if (dataModelPath == null)
|
||||
throw new ArgumentNullException(nameof(dataModelPath));
|
||||
|
||||
Target = dataModelPath.Target;
|
||||
Path = dataModelPath.Path;
|
||||
Entity = new DataModelPathEntity();
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class based on a <see cref="DataModelPathEntity" />
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public DataModelPath(DataModelPathEntity entity)
|
||||
{
|
||||
Path = entity.Path;
|
||||
Entity = entity;
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Load();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model at which this path starts
|
||||
/// </summary>
|
||||
public DataModel? Target { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model ID of the <see cref="Target" /> if it is a <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public string? DataModelId => Target?.Module.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point-separated path associated with this <see cref="DataModelPath" />
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether all <see cref="Segments" /> are valid
|
||||
/// </summary>
|
||||
public bool IsValid => Segments.Any() && Segments.All(p => p.Type != DataModelPathSegmentType.Invalid);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all segments of this path
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<DataModelPathSegment> Segments => _segments.ToList().AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity used for persistent storage
|
||||
/// </summary>
|
||||
public DataModelPathEntity Entity { get; }
|
||||
|
||||
internal Func<object, object>? Accessor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the path
|
||||
/// </summary>
|
||||
public object? GetValue()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
if (_accessorLambda == null || Target == null)
|
||||
return null;
|
||||
|
||||
// If the accessor has not yet been compiled do it now that it's first required
|
||||
if (Accessor == null)
|
||||
Accessor = _accessorLambda.Compile();
|
||||
return Accessor(Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property info of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If static, the property info. If dynamic, <c>null</c></returns>
|
||||
public PropertyInfo? GetPropertyInfo()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If possible, the property type</returns>
|
||||
public Type? GetPropertyType()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If found, the data model property description</returns>
|
||||
public DataModelPropertyAttribute? GetPropertyDescription()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the path becomes invalid
|
||||
/// </summary>
|
||||
public event EventHandler? PathInvalidated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the path becomes valid
|
||||
/// </summary>
|
||||
public event EventHandler? PathValidated;
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
|
||||
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="PathInvalidated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnPathValidated()
|
||||
{
|
||||
PathValidated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="PathValidated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnPathInvalidated()
|
||||
{
|
||||
PathInvalidated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal void Invalidate()
|
||||
{
|
||||
Target?.RemoveDataModelPath(this);
|
||||
|
||||
foreach (DataModelPathSegment dataModelPathSegment in _segments)
|
||||
dataModelPathSegment.Dispose();
|
||||
_segments.Clear();
|
||||
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
|
||||
OnPathInvalidated();
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
if (Target == null)
|
||||
return;
|
||||
|
||||
Target.AddDataModelPath(this);
|
||||
|
||||
DataModelPathSegment startSegment = new(this, "target", "target");
|
||||
startSegment.Node = _segments.AddFirst(startSegment);
|
||||
|
||||
// On an empty path don't bother processing segments
|
||||
if (!string.IsNullOrWhiteSpace(Path))
|
||||
{
|
||||
string[] segments = Path.Split(".");
|
||||
for (int index = 0; index < segments.Length; index++)
|
||||
{
|
||||
string identifier = segments[index];
|
||||
LinkedListNode<DataModelPathSegment> node = _segments.AddLast(
|
||||
new DataModelPathSegment(this, identifier, string.Join('.', segments.Take(index + 1)))
|
||||
);
|
||||
node.Value.Node = node;
|
||||
}
|
||||
}
|
||||
|
||||
ParameterExpression parameter = Expression.Parameter(typeof(object), "t");
|
||||
Expression? expression = Expression.Convert(parameter, Target.GetType());
|
||||
Expression? nullCondition = null;
|
||||
|
||||
MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!;
|
||||
foreach (DataModelPathSegment segment in _segments)
|
||||
{
|
||||
BinaryExpression notNull;
|
||||
try
|
||||
{
|
||||
notNull = Expression.NotEqual(expression, Expression.Default(expression.Type));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
notNull = Expression.NotEqual(
|
||||
Expression.Call(
|
||||
null,
|
||||
equals,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))),
|
||||
Expression.Constant(true));
|
||||
}
|
||||
|
||||
nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
|
||||
expression = segment.Initialize(parameter, expression, nullCondition);
|
||||
if (expression == null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullCondition == null)
|
||||
return;
|
||||
|
||||
_accessorLambda = Expression.Lambda<Func<object, object>>(
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
nullCondition,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))
|
||||
),
|
||||
parameter
|
||||
);
|
||||
|
||||
if (IsValid)
|
||||
OnPathValidated();
|
||||
}
|
||||
|
||||
private void SubscribeToDataModelStore()
|
||||
{
|
||||
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
|
||||
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
|
||||
}
|
||||
|
||||
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
|
||||
{
|
||||
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
|
||||
return;
|
||||
|
||||
Invalidate();
|
||||
Target = e.Registration.DataModel;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
|
||||
{
|
||||
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
|
||||
return;
|
||||
|
||||
Invalidate();
|
||||
Target = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
Path = Entity.Path;
|
||||
|
||||
if (Target == null && Entity.DataModelId != null)
|
||||
Target = DataModelStore.Get(Entity.DataModelId)?.DataModel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
// Do not save an invalid state
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
Entity.Path = Path;
|
||||
Entity.DataModelId = DataModelId;
|
||||
}
|
||||
|
||||
#region Equality members
|
||||
|
||||
/// <inheritdoc cref="Equals(object)"/>>
|
||||
protected bool Equals(DataModelPath other)
|
||||
{
|
||||
return ReferenceEquals(Target, other.Target) && Path == other.Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((DataModelPath) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Target, Path);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class pointing to the provided path
|
||||
/// </summary>
|
||||
/// <param name="target">The target at which this path starts</param>
|
||||
/// <param name="path">A point-separated path</param>
|
||||
public DataModelPath(DataModel target, string path)
|
||||
{
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
Entity = new DataModelPathEntity();
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class based on an existing path
|
||||
/// </summary>
|
||||
/// <param name="dataModelPath">The path to base the new instance on</param>
|
||||
public DataModelPath(DataModelPath dataModelPath)
|
||||
{
|
||||
if (dataModelPath == null)
|
||||
throw new ArgumentNullException(nameof(dataModelPath));
|
||||
|
||||
Target = dataModelPath.Target;
|
||||
Path = dataModelPath.Path;
|
||||
Entity = new DataModelPathEntity();
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class based on a <see cref="DataModelPathEntity" />
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public DataModelPath(DataModelPathEntity entity)
|
||||
{
|
||||
Path = entity.Path;
|
||||
Entity = entity;
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Load();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model at which this path starts
|
||||
/// </summary>
|
||||
public DataModel? Target { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model ID of the <see cref="Target" /> if it is a <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public string? DataModelId => Target?.Module.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point-separated path associated with this <see cref="DataModelPath" />
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether all <see cref="Segments" /> are valid
|
||||
/// </summary>
|
||||
public bool IsValid => Segments.Any() && Segments.All(p => p.Type != DataModelPathSegmentType.Invalid);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all segments of this path
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<DataModelPathSegment> Segments => _segments.ToList().AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity used for persistent storage
|
||||
/// </summary>
|
||||
public DataModelPathEntity Entity { get; }
|
||||
|
||||
internal Func<object, object>? Accessor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the path
|
||||
/// </summary>
|
||||
public object? GetValue()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
if (_accessorLambda == null || Target == null)
|
||||
return null;
|
||||
|
||||
// If the accessor has not yet been compiled do it now that it's first required
|
||||
if (Accessor == null)
|
||||
Accessor = _accessorLambda.Compile();
|
||||
return Accessor(Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property info of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If static, the property info. If dynamic, <c>null</c></returns>
|
||||
public PropertyInfo? GetPropertyInfo()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If possible, the property type</returns>
|
||||
public Type? GetPropertyType()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If found, the data model property description</returns>
|
||||
public DataModelPropertyAttribute? GetPropertyDescription()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the path becomes invalid
|
||||
/// </summary>
|
||||
public event EventHandler? PathInvalidated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the path becomes valid
|
||||
/// </summary>
|
||||
public event EventHandler? PathValidated;
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
|
||||
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="PathInvalidated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnPathValidated()
|
||||
{
|
||||
PathValidated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="PathValidated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnPathInvalidated()
|
||||
{
|
||||
PathInvalidated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal void Invalidate()
|
||||
{
|
||||
Target?.RemoveDataModelPath(this);
|
||||
|
||||
foreach (DataModelPathSegment dataModelPathSegment in _segments)
|
||||
dataModelPathSegment.Dispose();
|
||||
_segments.Clear();
|
||||
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
|
||||
OnPathInvalidated();
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
if (Target == null)
|
||||
return;
|
||||
|
||||
Target.AddDataModelPath(this);
|
||||
|
||||
DataModelPathSegment startSegment = new(this, "target", "target");
|
||||
startSegment.Node = _segments.AddFirst(startSegment);
|
||||
|
||||
// On an empty path don't bother processing segments
|
||||
if (!string.IsNullOrWhiteSpace(Path))
|
||||
{
|
||||
string[] segments = Path.Split(".");
|
||||
for (int index = 0; index < segments.Length; index++)
|
||||
{
|
||||
string identifier = segments[index];
|
||||
LinkedListNode<DataModelPathSegment> node = _segments.AddLast(
|
||||
new DataModelPathSegment(this, identifier, string.Join('.', segments.Take(index + 1)))
|
||||
);
|
||||
node.Value.Node = node;
|
||||
}
|
||||
}
|
||||
|
||||
ParameterExpression parameter = Expression.Parameter(typeof(object), "t");
|
||||
Expression? expression = Expression.Convert(parameter, Target.GetType());
|
||||
Expression? nullCondition = null;
|
||||
|
||||
MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!;
|
||||
foreach (DataModelPathSegment segment in _segments)
|
||||
{
|
||||
BinaryExpression notNull;
|
||||
try
|
||||
{
|
||||
notNull = Expression.NotEqual(expression, Expression.Default(expression.Type));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
notNull = Expression.NotEqual(
|
||||
Expression.Call(
|
||||
null,
|
||||
equals,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))),
|
||||
Expression.Constant(true));
|
||||
}
|
||||
|
||||
nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
|
||||
expression = segment.Initialize(parameter, expression, nullCondition);
|
||||
if (expression == null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullCondition == null)
|
||||
return;
|
||||
|
||||
_accessorLambda = Expression.Lambda<Func<object, object>>(
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
nullCondition,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))
|
||||
),
|
||||
parameter
|
||||
);
|
||||
|
||||
if (IsValid)
|
||||
OnPathValidated();
|
||||
}
|
||||
|
||||
private void SubscribeToDataModelStore()
|
||||
{
|
||||
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
|
||||
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
|
||||
}
|
||||
|
||||
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
|
||||
{
|
||||
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
|
||||
return;
|
||||
|
||||
Invalidate();
|
||||
Target = e.Registration.DataModel;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
|
||||
{
|
||||
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
|
||||
return;
|
||||
|
||||
Invalidate();
|
||||
Target = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
Path = Entity.Path;
|
||||
|
||||
if (Target == null && Entity.DataModelId != null)
|
||||
Target = DataModelStore.Get(Entity.DataModelId)?.DataModel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
// Do not save an invalid state
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
Entity.Path = Path;
|
||||
Entity.DataModelId = DataModelId;
|
||||
}
|
||||
|
||||
#region Equality members
|
||||
|
||||
/// <inheritdoc cref="Equals(object)" />
|
||||
/// >
|
||||
protected bool Equals(DataModelPath other)
|
||||
{
|
||||
return ReferenceEquals(Target, other.Target) && Path == other.Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((DataModelPath) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Target, Path);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -6,307 +6,294 @@ using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a segment of a data model path
|
||||
/// </summary>
|
||||
public class DataModelPathSegment : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a segment of a data model path
|
||||
/// </summary>
|
||||
public class DataModelPathSegment : IDisposable
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
private DataModel? _dynamicDataModel;
|
||||
private DataModelPropertyAttribute? _dynamicDataModelAttribute;
|
||||
private Type? _dynamicDataModelType;
|
||||
private PropertyInfo? _property;
|
||||
|
||||
internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path)
|
||||
{
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
private DataModel? _dynamicDataModel;
|
||||
private Type? _dynamicDataModelType;
|
||||
private DataModelPropertyAttribute? _dynamicDataModelAttribute;
|
||||
private PropertyInfo? _property;
|
||||
DataModelPath = dataModelPath;
|
||||
Identifier = identifier;
|
||||
Path = path;
|
||||
IsStartSegment = !DataModelPath.Segments.Any();
|
||||
}
|
||||
|
||||
internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path)
|
||||
/// <summary>
|
||||
/// Gets the data model path this is a segment of
|
||||
/// </summary>
|
||||
public DataModelPath DataModelPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier that is associated with this segment
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path that leads to this segment
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this is the first segment in the path
|
||||
/// </summary>
|
||||
public bool IsStartSegment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of data model this segment of the path points to
|
||||
/// </summary>
|
||||
public DataModelPathSegmentType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous segment in the path
|
||||
/// </summary>
|
||||
public DataModelPathSegment? Previous => Node?.Previous?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next segment in the path
|
||||
/// </summary>
|
||||
public DataModelPathSegment? Next => Node?.Next?.Value;
|
||||
|
||||
internal Func<object, object>? Accessor { get; set; }
|
||||
internal LinkedListNode<DataModelPathSegment>? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current value of the path up to this segment
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object? GetValue()
|
||||
{
|
||||
if (Type == DataModelPathSegmentType.Invalid || DataModelPath.Target == null || _accessorLambda == null)
|
||||
return null;
|
||||
|
||||
// If the accessor has not yet been compiled do it now that it's first required
|
||||
if (Accessor == null)
|
||||
Accessor = _accessorLambda.Compile();
|
||||
return Accessor(DataModelPath.Target);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Type}] {Path}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property info of the property this segment points to
|
||||
/// </summary>
|
||||
/// <returns>If static, the property info. If dynamic, <c>null</c></returns>
|
||||
public PropertyInfo? GetPropertyInfo()
|
||||
{
|
||||
// Dynamic types have no property and therefore no property info
|
||||
if (Type == DataModelPathSegmentType.Dynamic)
|
||||
return null;
|
||||
// The start segment has none either because it is the datamodel
|
||||
if (IsStartSegment)
|
||||
return null;
|
||||
|
||||
// If this is not the first segment in a path, the property is located on the previous segment
|
||||
return Previous?.GetPropertyType()?.GetProperties().FirstOrDefault(p => p.Name == Identifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of the property this segment points to
|
||||
/// </summary>
|
||||
/// <returns>If found, the data model property description</returns>
|
||||
public DataModelPropertyAttribute? GetPropertyDescription()
|
||||
{
|
||||
// Dynamic types have a data model description
|
||||
if (Type == DataModelPathSegmentType.Dynamic)
|
||||
return _dynamicDataModelAttribute;
|
||||
if (IsStartSegment && DataModelPath.Target != null)
|
||||
return DataModelPath.Target.DataModelDescription;
|
||||
if (IsStartSegment)
|
||||
return null;
|
||||
|
||||
PropertyInfo? propertyInfo = GetPropertyInfo();
|
||||
if (propertyInfo == null)
|
||||
return null;
|
||||
|
||||
// Static types may have one as an attribute
|
||||
DataModelPropertyAttribute? attribute = DataModelPath.Target?.GetPropertyDescription(propertyInfo);
|
||||
if (attribute != null)
|
||||
{
|
||||
DataModelPath = dataModelPath;
|
||||
Identifier = identifier;
|
||||
Path = path;
|
||||
IsStartSegment = !DataModelPath.Segments.Any();
|
||||
if (string.IsNullOrWhiteSpace(attribute.Name))
|
||||
attribute.Name = propertyInfo.Name.Humanize();
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model path this is a segment of
|
||||
/// </summary>
|
||||
public DataModelPath DataModelPath { get; }
|
||||
return new DataModelPropertyAttribute {Name = propertyInfo.Name.Humanize(), ResetsDepth = false};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier that is associated with this segment
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
/// <summary>
|
||||
/// Gets the type of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If possible, the property type</returns>
|
||||
public Type? GetPropertyType()
|
||||
{
|
||||
// The start segment type is always the target type
|
||||
if (IsStartSegment)
|
||||
return DataModelPath.Target?.GetType();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path that leads to this segment
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this is the first segment in the path
|
||||
/// </summary>
|
||||
public bool IsStartSegment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of data model this segment of the path points to
|
||||
/// </summary>
|
||||
public DataModelPathSegmentType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous segment in the path
|
||||
/// </summary>
|
||||
public DataModelPathSegment? Previous => Node?.Previous?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next segment in the path
|
||||
/// </summary>
|
||||
public DataModelPathSegment? Next => Node?.Next?.Value;
|
||||
|
||||
internal Func<object, object>? Accessor { get; set; }
|
||||
internal LinkedListNode<DataModelPathSegment>? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current value of the path up to this segment
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object? GetValue()
|
||||
// Prefer basing the type on the property info
|
||||
PropertyInfo? propertyInfo = GetPropertyInfo();
|
||||
Type? type = propertyInfo?.PropertyType;
|
||||
// Property info is not available on dynamic paths though, so fall back on the current value
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
if (Type == DataModelPathSegmentType.Invalid || DataModelPath.Target == null || _accessorLambda == null)
|
||||
return null;
|
||||
|
||||
// If the accessor has not yet been compiled do it now that it's first required
|
||||
if (Accessor == null)
|
||||
Accessor = _accessorLambda.Compile();
|
||||
return Accessor(DataModelPath.Target);
|
||||
object? currentValue = GetValue();
|
||||
if (currentValue != null)
|
||||
type = currentValue.GetType();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
return type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
return $"[{Type}] {Path}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property info of the property this segment points to
|
||||
/// </summary>
|
||||
/// <returns>If static, the property info. If dynamic, <c>null</c></returns>
|
||||
public PropertyInfo? GetPropertyInfo()
|
||||
{
|
||||
// Dynamic types have no property and therefore no property info
|
||||
if (Type == DataModelPathSegmentType.Dynamic)
|
||||
return null;
|
||||
// The start segment has none either because it is the datamodel
|
||||
if (IsStartSegment)
|
||||
return null;
|
||||
|
||||
// If this is not the first segment in a path, the property is located on the previous segment
|
||||
return Previous?.GetPropertyType()?.GetProperties().FirstOrDefault(p => p.Name == Identifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of the property this segment points to
|
||||
/// </summary>
|
||||
/// <returns>If found, the data model property description</returns>
|
||||
public DataModelPropertyAttribute? GetPropertyDescription()
|
||||
{
|
||||
// Dynamic types have a data model description
|
||||
if (Type == DataModelPathSegmentType.Dynamic)
|
||||
return _dynamicDataModelAttribute;
|
||||
if (IsStartSegment && DataModelPath.Target != null)
|
||||
return DataModelPath.Target.DataModelDescription;
|
||||
if (IsStartSegment)
|
||||
return null;
|
||||
|
||||
PropertyInfo? propertyInfo = GetPropertyInfo();
|
||||
if (propertyInfo == null)
|
||||
return null;
|
||||
|
||||
// Static types may have one as an attribute
|
||||
DataModelPropertyAttribute? attribute = DataModelPath.Target?.GetPropertyDescription(propertyInfo);
|
||||
if (attribute != null)
|
||||
if (_dynamicDataModel != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(attribute.Name))
|
||||
attribute.Name = propertyInfo.Name.Humanize();
|
||||
return attribute;
|
||||
_dynamicDataModel.DynamicChildAdded -= DynamicChildOnDynamicChildAdded;
|
||||
_dynamicDataModel.DynamicChildRemoved -= DynamicChildOnDynamicChildRemoved;
|
||||
}
|
||||
|
||||
return new DataModelPropertyAttribute {Name = propertyInfo.Name.Humanize(), ResetsDepth = false};
|
||||
Type = DataModelPathSegmentType.Invalid;
|
||||
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If possible, the property type</returns>
|
||||
public Type? GetPropertyType()
|
||||
internal Expression? Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
if (IsStartSegment)
|
||||
{
|
||||
// The start segment type is always the target type
|
||||
if (IsStartSegment)
|
||||
return DataModelPath.Target?.GetType();
|
||||
|
||||
// Prefer basing the type on the property info
|
||||
PropertyInfo? propertyInfo = GetPropertyInfo();
|
||||
Type? type = propertyInfo?.PropertyType;
|
||||
// Property info is not available on dynamic paths though, so fall back on the current value
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
object? currentValue = GetValue();
|
||||
if (currentValue != null)
|
||||
type = currentValue.GetType();
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
internal Expression? Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
if (IsStartSegment)
|
||||
{
|
||||
Type = DataModelPathSegmentType.Static;
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
Type? previousType = Previous?.GetPropertyType();
|
||||
if (previousType == null)
|
||||
{
|
||||
Type = DataModelPathSegmentType.Invalid;
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
// Prefer static since that's faster
|
||||
DetermineStaticType(previousType);
|
||||
|
||||
// If no static type could be found, check if this is a data model and if so, look for a dynamic type
|
||||
if (Type == DataModelPathSegmentType.Invalid && typeof(DataModel).IsAssignableFrom(previousType))
|
||||
{
|
||||
_dynamicDataModel = Previous?.GetValue() as DataModel;
|
||||
// Cannot determine a dynamic type on a null data model, leave the segment invalid
|
||||
if (_dynamicDataModel == null)
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
|
||||
// If a dynamic data model is found the use that
|
||||
bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild);
|
||||
if (hasDynamicChild && dynamicChild?.BaseValue != null)
|
||||
DetermineDynamicType(dynamicChild.BaseValue, dynamicChild.Attribute);
|
||||
|
||||
_dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded;
|
||||
_dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved;
|
||||
}
|
||||
|
||||
Type = DataModelPathSegmentType.Static;
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
private Expression? CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
Type? previousType = Previous?.GetPropertyType();
|
||||
if (previousType == null)
|
||||
{
|
||||
if (Type == DataModelPathSegmentType.Invalid)
|
||||
{
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
return null;
|
||||
}
|
||||
Type = DataModelPathSegmentType.Invalid;
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
Expression accessorExpression;
|
||||
// A start segment just accesses the target
|
||||
if (IsStartSegment)
|
||||
accessorExpression = expression;
|
||||
// A static segment just needs to access the property or filed
|
||||
else if (Type == DataModelPathSegmentType.Static)
|
||||
{
|
||||
accessorExpression = _property != null
|
||||
? Expression.Property(expression, _property)
|
||||
: Expression.PropertyOrField(expression, Identifier);
|
||||
}
|
||||
// A dynamic segment calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument
|
||||
else
|
||||
{
|
||||
accessorExpression = Expression.Call(
|
||||
expression,
|
||||
nameof(DataModel.GetDynamicChildValue),
|
||||
_dynamicDataModelType != null ? new[] {_dynamicDataModelType} : null,
|
||||
Expression.Constant(Identifier)
|
||||
);
|
||||
}
|
||||
// Prefer static since that's faster
|
||||
DetermineStaticType(previousType);
|
||||
|
||||
_accessorLambda = Expression.Lambda<Func<object, object>>(
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
nullCondition,
|
||||
Expression.Convert(accessorExpression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(accessorExpression.Type), typeof(object))
|
||||
),
|
||||
parameter
|
||||
);
|
||||
// If no static type could be found, check if this is a data model and if so, look for a dynamic type
|
||||
if (Type == DataModelPathSegmentType.Invalid && typeof(DataModel).IsAssignableFrom(previousType))
|
||||
{
|
||||
_dynamicDataModel = Previous?.GetValue() as DataModel;
|
||||
// Cannot determine a dynamic type on a null data model, leave the segment invalid
|
||||
if (_dynamicDataModel == null)
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
|
||||
// If a dynamic data model is found the use that
|
||||
bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild);
|
||||
if (hasDynamicChild && dynamicChild?.BaseValue != null)
|
||||
DetermineDynamicType(dynamicChild.BaseValue, dynamicChild.Attribute);
|
||||
|
||||
_dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded;
|
||||
_dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved;
|
||||
}
|
||||
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
private Expression? CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
if (Type == DataModelPathSegmentType.Invalid)
|
||||
{
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
return accessorExpression;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void DetermineDynamicType(object dynamicDataModel, DataModelPropertyAttribute attribute)
|
||||
Expression accessorExpression;
|
||||
// A start segment just accesses the target
|
||||
if (IsStartSegment)
|
||||
accessorExpression = expression;
|
||||
// A static segment just needs to access the property or filed
|
||||
else if (Type == DataModelPathSegmentType.Static)
|
||||
accessorExpression = _property != null
|
||||
? Expression.Property(expression, _property)
|
||||
: Expression.PropertyOrField(expression, Identifier);
|
||||
// A dynamic segment calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument
|
||||
else
|
||||
accessorExpression = Expression.Call(
|
||||
expression,
|
||||
nameof(DataModel.GetDynamicChildValue),
|
||||
_dynamicDataModelType != null ? new[] {_dynamicDataModelType} : null,
|
||||
Expression.Constant(Identifier)
|
||||
);
|
||||
|
||||
_accessorLambda = Expression.Lambda<Func<object, object>>(
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
nullCondition,
|
||||
Expression.Convert(accessorExpression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(accessorExpression.Type), typeof(object))
|
||||
),
|
||||
parameter
|
||||
);
|
||||
Accessor = null;
|
||||
return accessorExpression;
|
||||
}
|
||||
|
||||
private void DetermineDynamicType(object dynamicDataModel, DataModelPropertyAttribute attribute)
|
||||
{
|
||||
Type = DataModelPathSegmentType.Dynamic;
|
||||
_dynamicDataModelType = dynamicDataModel.GetType();
|
||||
_dynamicDataModelAttribute = attribute;
|
||||
}
|
||||
|
||||
private void DetermineStaticType(Type previousType)
|
||||
{
|
||||
// Situations in which AmbiguousMatchException occurs ...
|
||||
//
|
||||
// ...derived type declares a property that hides an inherited property with the same name, by using the new modifier
|
||||
_property = previousType.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(p => p.Name == Identifier);
|
||||
Type = _property == null ? DataModelPathSegmentType.Invalid : DataModelPathSegmentType.Static;
|
||||
}
|
||||
|
||||
private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e)
|
||||
{
|
||||
if (e.Key == Identifier)
|
||||
{
|
||||
Type = DataModelPathSegmentType.Dynamic;
|
||||
_dynamicDataModelType = dynamicDataModel.GetType();
|
||||
_dynamicDataModelAttribute = attribute;
|
||||
DataModelPath.Invalidate();
|
||||
DataModelPath.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void DetermineStaticType(Type previousType)
|
||||
{
|
||||
// Situations in which AmbiguousMatchException occurs ...
|
||||
//
|
||||
// ...derived type declares a property that hides an inherited property with the same name, by using the new modifier
|
||||
_property = previousType.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(p => p.Name == Identifier);
|
||||
Type = _property == null ? DataModelPathSegmentType.Invalid : DataModelPathSegmentType.Static;
|
||||
}
|
||||
private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e)
|
||||
{
|
||||
if (e.DynamicChild.BaseValue == _dynamicDataModel)
|
||||
DataModelPath.Invalidate();
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_dynamicDataModel != null)
|
||||
{
|
||||
_dynamicDataModel.DynamicChildAdded -= DynamicChildOnDynamicChildAdded;
|
||||
_dynamicDataModel.DynamicChildRemoved -= DynamicChildOnDynamicChildRemoved;
|
||||
}
|
||||
|
||||
Type = DataModelPathSegmentType.Invalid;
|
||||
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e)
|
||||
{
|
||||
if (e.Key == Identifier)
|
||||
{
|
||||
DataModelPath.Invalidate();
|
||||
DataModelPath.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e)
|
||||
{
|
||||
if (e.DynamicChild.BaseValue == _dynamicDataModel)
|
||||
DataModelPath.Invalidate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,22 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a type of data model path
|
||||
/// </summary>
|
||||
public enum DataModelPathSegmentType
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a type of data model path
|
||||
/// Represents an invalid data model type that points to a missing data model
|
||||
/// </summary>
|
||||
public enum DataModelPathSegmentType
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an invalid data model type that points to a missing data model
|
||||
/// </summary>
|
||||
Invalid,
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a static data model type that points to a data model defined in code
|
||||
/// </summary>
|
||||
Static,
|
||||
/// <summary>
|
||||
/// Represents a static data model type that points to a data model defined in code
|
||||
/// </summary>
|
||||
Static,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a static data model type that points to a data model defined at runtime
|
||||
/// </summary>
|
||||
Dynamic
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a static data model type that points to a data model defined at runtime
|
||||
/// </summary>
|
||||
Dynamic
|
||||
}
|
||||
@ -1,69 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an event that is part of a data model
|
||||
/// </summary>
|
||||
public interface IDataModelEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an event that is part of a data model
|
||||
/// Gets the last time the event was triggered
|
||||
/// </summary>
|
||||
public interface IDataModelEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the last time the event was triggered
|
||||
/// </summary>
|
||||
DateTime LastTrigger { get; }
|
||||
DateTime LastTrigger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time that has passed since the last trigger
|
||||
/// </summary>
|
||||
TimeSpan TimeSinceLastTrigger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of times the event was triggered
|
||||
/// </summary>
|
||||
int TriggerCount { get; }
|
||||
/// <summary>
|
||||
/// Gets the time that has passed since the last trigger
|
||||
/// </summary>
|
||||
TimeSpan TimeSinceLastTrigger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of arguments this event contains
|
||||
/// </summary>
|
||||
Type ArgumentsType { get; }
|
||||
/// <summary>
|
||||
/// Gets the amount of times the event was triggered
|
||||
/// </summary>
|
||||
int TriggerCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the past participle for this event shown in the UI
|
||||
/// </summary>
|
||||
string TriggerPastParticiple { get; }
|
||||
/// <summary>
|
||||
/// Gets the type of arguments this event contains
|
||||
/// </summary>
|
||||
Type ArgumentsType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the last 20 events should be tracked
|
||||
/// <para>Note: setting this to <see langword="false" /> will clear the current history</para>
|
||||
/// </summary>
|
||||
bool TrackHistory { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the past participle for this event shown in the UI
|
||||
/// </summary>
|
||||
string TriggerPastParticiple { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event arguments of the last time the event was triggered by its base type
|
||||
/// </summary>
|
||||
public DataModelEventArgs? LastEventArgumentsUntyped { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the last 20 events should be tracked
|
||||
/// <para>Note: setting this to <see langword="false" /> will clear the current history</para>
|
||||
/// </summary>
|
||||
bool TrackHistory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the last 20 event arguments by their base type.
|
||||
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||
/// </summary>
|
||||
public List<DataModelEventArgs> EventArgumentsHistoryUntyped { get; }
|
||||
/// <summary>
|
||||
/// Gets the event arguments of the last time the event was triggered by its base type
|
||||
/// </summary>
|
||||
public DataModelEventArgs? LastEventArgumentsUntyped { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the event is triggered
|
||||
/// </summary>
|
||||
event EventHandler EventTriggered;
|
||||
/// <summary>
|
||||
/// Gets a list of the last 20 event arguments by their base type.
|
||||
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||
/// </summary>
|
||||
public List<DataModelEventArgs> EventArgumentsHistoryUntyped { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resets the trigger count and history of this data model event
|
||||
/// </summary>
|
||||
void Reset();
|
||||
/// <summary>
|
||||
/// Fires when the event is triggered
|
||||
/// </summary>
|
||||
event EventHandler EventTriggered;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the event, not required for standard events but included in case your custom event needs to update every
|
||||
/// tick
|
||||
/// </summary>
|
||||
void Update();
|
||||
}
|
||||
/// <summary>
|
||||
/// Resets the trigger count and history of this data model event
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the event, not required for standard events but included in case your custom event needs to update every
|
||||
/// tick
|
||||
/// </summary>
|
||||
void Update();
|
||||
}
|
||||
@ -1,360 +1,359 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a folder in a <see cref="Profile" />
|
||||
/// </summary>
|
||||
public sealed class Folder : RenderProfileElement
|
||||
{
|
||||
private bool _isExpanded;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a folder in a <see cref="Profile" />
|
||||
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
|
||||
/// <paramref name="parent" />
|
||||
/// </summary>
|
||||
public sealed class Folder : RenderProfileElement
|
||||
/// <param name="parent">The parent of the folder</param>
|
||||
/// <param name="name">The name of the folder</param>
|
||||
public Folder(ProfileElement parent, string name) : base(parent, parent.Profile)
|
||||
{
|
||||
private bool _isExpanded;
|
||||
FolderEntity = new FolderEntity();
|
||||
EntityId = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
|
||||
/// <paramref name="parent" />
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent of the folder</param>
|
||||
/// <param name="name">The name of the folder</param>
|
||||
public Folder(ProfileElement parent, string name) : base(parent, parent.Profile)
|
||||
Profile = Parent.Profile;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Folder" /> class based on the provided folder entity
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile the folder belongs to</param>
|
||||
/// <param name="parent">The parent of the folder</param>
|
||||
/// <param name="folderEntity">The entity of the folder</param>
|
||||
public Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) : base(parent, parent.Profile)
|
||||
{
|
||||
FolderEntity = folderEntity;
|
||||
EntityId = folderEntity.Id;
|
||||
|
||||
Profile = profile;
|
||||
Name = folderEntity.Name;
|
||||
IsExpanded = folderEntity.IsExpanded;
|
||||
Suspended = folderEntity.Suspended;
|
||||
Order = folderEntity.Order;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this folder is at the root of the profile tree
|
||||
/// </summary>
|
||||
public bool IsRootFolder => Parent == Profile;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this folder is expanded
|
||||
/// </summary>
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetAndNotify(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder entity this folder uses for persistent storage
|
||||
/// </summary>
|
||||
public FolderEntity FolderEntity { get; internal set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet && !Timeline.IsFinished;
|
||||
|
||||
internal override RenderElementEntity RenderElementEntity => FolderEntity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override List<ILayerProperty> GetAllLayerProperties()
|
||||
{
|
||||
List<ILayerProperty> result = new();
|
||||
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
||||
{
|
||||
FolderEntity = new FolderEntity();
|
||||
EntityId = Guid.NewGuid();
|
||||
|
||||
Profile = Parent.Profile;
|
||||
Name = name;
|
||||
if (layerEffect.BaseProperties != null)
|
||||
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Folder" /> class based on the provided folder entity
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile the folder belongs to</param>
|
||||
/// <param name="parent">The parent of the folder</param>
|
||||
/// <param name="folderEntity">The entity of the folder</param>
|
||||
public Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) : base(parent, parent.Profile)
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
if (Timeline.IsOverridden)
|
||||
{
|
||||
FolderEntity = folderEntity;
|
||||
EntityId = folderEntity.Id;
|
||||
|
||||
Profile = profile;
|
||||
Name = folderEntity.Name;
|
||||
IsExpanded = folderEntity.IsExpanded;
|
||||
Suspended = folderEntity.Suspended;
|
||||
Order = folderEntity.Order;
|
||||
|
||||
Load();
|
||||
Timeline.ClearOverride();
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this folder is at the root of the profile tree
|
||||
/// </summary>
|
||||
public bool IsRootFolder => Parent == Profile;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this folder is expanded
|
||||
/// </summary>
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetAndNotify(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder entity this folder uses for persistent storage
|
||||
/// </summary>
|
||||
public FolderEntity FolderEntity { get; internal set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet && !Timeline.IsFinished;
|
||||
|
||||
internal override RenderElementEntity RenderElementEntity => FolderEntity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override List<ILayerProperty> GetAllLayerProperties()
|
||||
{
|
||||
List<ILayerProperty> result = new();
|
||||
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
||||
{
|
||||
if (layerEffect.BaseProperties != null)
|
||||
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
if (Timeline.IsOverridden)
|
||||
{
|
||||
Timeline.ClearOverride();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateDisplayCondition();
|
||||
UpdateTimeline(deltaTime);
|
||||
|
||||
if (ShouldBeEnabled)
|
||||
Enable();
|
||||
else if (Timeline.IsFinished)
|
||||
Disable();
|
||||
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline);
|
||||
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Update(deltaTime);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Reset()
|
||||
{
|
||||
UpdateDisplayCondition();
|
||||
|
||||
if (DisplayConditionMet)
|
||||
Timeline.JumpToStart();
|
||||
else
|
||||
Timeline.JumpToEnd();
|
||||
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddChild(ProfileElement child, int? order = null)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
base.AddChild(child, order);
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RemoveChild(ProfileElement child)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
base.RemoveChild(child);
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep copy of the folder
|
||||
/// </summary>
|
||||
/// <returns>The newly created copy</returns>
|
||||
public Folder CreateCopy()
|
||||
{
|
||||
if (Parent == null)
|
||||
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
|
||||
|
||||
FolderEntity entityCopy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(FolderEntity, true), true)!;
|
||||
entityCopy.Id = Guid.NewGuid();
|
||||
entityCopy.Name += " - Copy";
|
||||
|
||||
// TODO Children
|
||||
|
||||
return new Folder(Profile, Parent, entityCopy);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||
}
|
||||
|
||||
#region Rendering
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
// Ensure the folder is ready
|
||||
if (!Enabled || Path == null)
|
||||
return;
|
||||
|
||||
// No point rendering if all children are disabled
|
||||
if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
|
||||
return;
|
||||
|
||||
// If the editor focus is on this folder, discard further focus for children to effectively focus the entire folder and all descendants
|
||||
if (editorFocus == this)
|
||||
editorFocus = null;
|
||||
|
||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||
try
|
||||
{
|
||||
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
{
|
||||
if (!baseLayerEffect.Suspended)
|
||||
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
|
||||
}
|
||||
|
||||
// No point rendering if the alpha was set to zero by one of the effects
|
||||
if (layerPaint.Color.Alpha == 0)
|
||||
return;
|
||||
|
||||
canvas.SaveLayer(layerPaint);
|
||||
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
||||
|
||||
// Iterate the children in reverse because the first layer must be rendered last to end up on top
|
||||
for (int index = Children.Count - 1; index > -1; index--)
|
||||
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top), editorFocus);
|
||||
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
{
|
||||
if (!baseLayerEffect.Suspended)
|
||||
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
canvas.Restore();
|
||||
layerPaint.DisposeSelfAndProperties();
|
||||
}
|
||||
|
||||
Timeline.ClearDelta();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Enable()
|
||||
{
|
||||
// No checks here, effects will do their own checks to ensure they never enable twice
|
||||
// Also not enabling children, they'll enable themselves during their own Update
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
baseLayerEffect.InternalEnable();
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Disable()
|
||||
{
|
||||
// No checks here, effects will do their own checks to ensure they never disable twice
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
baseLayerEffect.InternalDisable();
|
||||
|
||||
// Disabling children since their Update won't get called with their parent disabled
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
{
|
||||
if (profileElement is RenderProfileElement renderProfileElement)
|
||||
renderProfileElement.Disable();
|
||||
}
|
||||
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OverrideTimelineAndApply(TimeSpan position)
|
||||
{
|
||||
DisplayCondition.OverrideTimeline(position);
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property affecting the rendering properties of this folder has been updated
|
||||
/// </summary>
|
||||
public event EventHandler? RenderPropertiesUpdated;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
Disposed = true;
|
||||
UpdateDisplayCondition();
|
||||
UpdateTimeline(deltaTime);
|
||||
|
||||
if (ShouldBeEnabled)
|
||||
Enable();
|
||||
else if (Timeline.IsFinished)
|
||||
Disable();
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline);
|
||||
|
||||
internal void CalculateRenderProperties()
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Update(deltaTime);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Reset()
|
||||
{
|
||||
UpdateDisplayCondition();
|
||||
|
||||
if (DisplayConditionMet)
|
||||
Timeline.JumpToStart();
|
||||
else
|
||||
Timeline.JumpToEnd();
|
||||
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddChild(ProfileElement child, int? order = null)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
base.AddChild(child, order);
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RemoveChild(ProfileElement child)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
base.RemoveChild(child);
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep copy of the folder
|
||||
/// </summary>
|
||||
/// <returns>The newly created copy</returns>
|
||||
public Folder CreateCopy()
|
||||
{
|
||||
if (Parent == null)
|
||||
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
|
||||
|
||||
FolderEntity entityCopy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(FolderEntity, true), true)!;
|
||||
entityCopy.Id = Guid.NewGuid();
|
||||
entityCopy.Name += " - Copy";
|
||||
|
||||
// TODO Children
|
||||
|
||||
return new Folder(Profile, Parent, entityCopy);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||
}
|
||||
|
||||
#region Rendering
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
// Ensure the folder is ready
|
||||
if (!Enabled || Path == null)
|
||||
return;
|
||||
|
||||
// No point rendering if all children are disabled
|
||||
if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
|
||||
return;
|
||||
|
||||
// If the editor focus is on this folder, discard further focus for children to effectively focus the entire folder and all descendants
|
||||
if (editorFocus == this)
|
||||
editorFocus = null;
|
||||
|
||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||
try
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
SKPath path = new() {FillType = SKPathFillType.Winding};
|
||||
foreach (ProfileElement child in Children)
|
||||
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
{
|
||||
if (child is RenderProfileElement effectChild && effectChild.Path != null)
|
||||
path.AddPath(effectChild.Path);
|
||||
if (!baseLayerEffect.Suspended)
|
||||
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
|
||||
}
|
||||
|
||||
Path = path;
|
||||
// No point rendering if the alpha was set to zero by one of the effects
|
||||
if (layerPaint.Color.Alpha == 0)
|
||||
return;
|
||||
|
||||
// Folder render properties are based on child paths and thus require an update
|
||||
if (Parent is Folder folder)
|
||||
folder.CalculateRenderProperties();
|
||||
canvas.SaveLayer(layerPaint);
|
||||
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
||||
|
||||
OnRenderPropertiesUpdated();
|
||||
// Iterate the children in reverse because the first layer must be rendered last to end up on top
|
||||
for (int index = Children.Count - 1; index > -1; index--)
|
||||
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top), editorFocus);
|
||||
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
{
|
||||
if (!baseLayerEffect.Suspended)
|
||||
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Load()
|
||||
finally
|
||||
{
|
||||
Reset();
|
||||
|
||||
// Load child folders
|
||||
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
|
||||
ChildrenList.Add(new Folder(Profile, this, childFolder));
|
||||
// Load child layers
|
||||
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
|
||||
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
||||
|
||||
// Ensure order integrity, should be unnecessary but no one is perfect specially me
|
||||
ChildrenList.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||
for (int index = 0; index < ChildrenList.Count; index++)
|
||||
ChildrenList[index].Order = index + 1;
|
||||
|
||||
LoadRenderElement();
|
||||
canvas.Restore();
|
||||
layerPaint.DisposeSelfAndProperties();
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
Timeline.ClearDelta();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Enable()
|
||||
{
|
||||
// No checks here, effects will do their own checks to ensure they never enable twice
|
||||
// Also not enabling children, they'll enable themselves during their own Update
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
baseLayerEffect.InternalEnable();
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Disable()
|
||||
{
|
||||
// No checks here, effects will do their own checks to ensure they never disable twice
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||
baseLayerEffect.InternalDisable();
|
||||
|
||||
// Disabling children since their Update won't get called with their parent disabled
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
FolderEntity.Id = EntityId;
|
||||
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
|
||||
|
||||
FolderEntity.Order = Order;
|
||||
FolderEntity.Name = Name;
|
||||
FolderEntity.IsExpanded = IsExpanded;
|
||||
FolderEntity.Suspended = Suspended;
|
||||
|
||||
FolderEntity.ProfileId = Profile.EntityId;
|
||||
|
||||
SaveRenderElement();
|
||||
if (profileElement is RenderProfileElement renderProfileElement)
|
||||
renderProfileElement.Disable();
|
||||
}
|
||||
|
||||
private void OnRenderPropertiesUpdated()
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OverrideTimelineAndApply(TimeSpan position)
|
||||
{
|
||||
DisplayCondition.OverrideTimeline(position);
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline);
|
||||
;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property affecting the rendering properties of this folder has been updated
|
||||
/// </summary>
|
||||
public event EventHandler? RenderPropertiesUpdated;
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<IBreakableModel> GetBrokenHierarchy()
|
||||
{
|
||||
return LayerEffects.Where(e => e.BrokenState != null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
Disposed = true;
|
||||
|
||||
Disable();
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
internal void CalculateRenderProperties()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
SKPath path = new() {FillType = SKPathFillType.Winding};
|
||||
foreach (ProfileElement child in Children)
|
||||
{
|
||||
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
|
||||
if (child is RenderProfileElement effectChild && effectChild.Path != null)
|
||||
path.AddPath(effectChild.Path);
|
||||
}
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
Path = path;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<IBreakableModel> GetBrokenHierarchy()
|
||||
{
|
||||
return LayerEffects.Where(e => e.BrokenState != null);
|
||||
}
|
||||
// Folder render properties are based on child paths and thus require an update
|
||||
if (Parent is Folder folder)
|
||||
folder.CalculateRenderProperties();
|
||||
|
||||
#endregion
|
||||
OnRenderPropertiesUpdated();
|
||||
}
|
||||
|
||||
internal override void Load()
|
||||
{
|
||||
Reset();
|
||||
|
||||
// Load child folders
|
||||
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
|
||||
ChildrenList.Add(new Folder(Profile, this, childFolder));
|
||||
// Load child layers
|
||||
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
|
||||
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
||||
|
||||
// Ensure order integrity, should be unnecessary but no one is perfect specially me
|
||||
ChildrenList.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||
for (int index = 0; index < ChildrenList.Count; index++)
|
||||
ChildrenList[index].Order = index + 1;
|
||||
|
||||
LoadRenderElement();
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Folder");
|
||||
|
||||
FolderEntity.Id = EntityId;
|
||||
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
|
||||
|
||||
FolderEntity.Order = Order;
|
||||
FolderEntity.Name = Name;
|
||||
FolderEntity.IsExpanded = IsExpanded;
|
||||
FolderEntity.Suspended = Suspended;
|
||||
|
||||
FolderEntity.ProfileId = Profile.EntityId;
|
||||
|
||||
SaveRenderElement();
|
||||
}
|
||||
|
||||
private void OnRenderPropertiesUpdated()
|
||||
{
|
||||
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,194 +6,195 @@ using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an adapter that adapts a layer to a certain set of devices using <see cref="IAdaptionHint" />s
|
||||
/// </summary>
|
||||
public class LayerAdapter : IStorageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an adapter that adapts a layer to a certain set of devices using <see cref="IAdaptionHint" />s
|
||||
/// </summary>
|
||||
public class LayerAdapter : IStorageModel
|
||||
private readonly List<IAdaptionHint> _adaptionHints;
|
||||
|
||||
internal LayerAdapter(Layer layer)
|
||||
{
|
||||
private readonly List<IAdaptionHint> _adaptionHints;
|
||||
|
||||
internal LayerAdapter(Layer layer)
|
||||
{
|
||||
_adaptionHints = new List<IAdaptionHint>();
|
||||
Layer = layer;
|
||||
AdaptionHints = new ReadOnlyCollection<IAdaptionHint>(_adaptionHints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer this adapter can adapt
|
||||
/// </summary>
|
||||
public Layer Layer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list containing the adaption hints used by this adapter
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<IAdaptionHint> AdaptionHints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the layer, adapting it to the provided <paramref name="devices" />
|
||||
/// </summary>
|
||||
/// <param name="devices">The devices to adapt the layer to</param>
|
||||
public void Adapt(List<ArtemisDevice> devices)
|
||||
{
|
||||
// Use adaption hints if provided
|
||||
if (AdaptionHints.Any())
|
||||
{
|
||||
foreach (IAdaptionHint adaptionHint in AdaptionHints)
|
||||
adaptionHint.Apply(Layer, devices);
|
||||
}
|
||||
// If there are no hints, try to find matching LEDs anyway
|
||||
else
|
||||
{
|
||||
List<ArtemisLed> availableLeds = devices.SelectMany(d => d.Leds).ToList();
|
||||
List<ArtemisLed> usedLeds = new();
|
||||
|
||||
foreach (LedEntity ledEntity in Layer.LayerEntity.Leds)
|
||||
{
|
||||
// TODO: If this is a keyboard LED and the layouts don't match, convert it before looking for it on the devices
|
||||
|
||||
LedId ledId = Enum.Parse<LedId>(ledEntity.LedName);
|
||||
ArtemisLed? led = availableLeds.FirstOrDefault(l => l.RgbLed.Id == ledId);
|
||||
|
||||
if (led != null)
|
||||
{
|
||||
availableLeds.Remove(led);
|
||||
usedLeds.Add(led);
|
||||
}
|
||||
}
|
||||
|
||||
Layer.AddLeds(usedLeds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically determine hints for this layer
|
||||
/// </summary>
|
||||
public List<IAdaptionHint> DetermineHints(IEnumerable<ArtemisDevice> devices)
|
||||
{
|
||||
List<IAdaptionHint> newHints = new();
|
||||
if (devices.All(DoesLayerCoverDevice))
|
||||
{
|
||||
DeviceAdaptionHint hint = new() {DeviceType = RGBDeviceType.All};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any fully covered device will add a device adaption hint for that type
|
||||
foreach (IGrouping<ArtemisDevice, ArtemisLed> deviceLeds in Layer.Leds.GroupBy(l => l.Device))
|
||||
{
|
||||
ArtemisDevice device = deviceLeds.Key;
|
||||
// If there is already an adaption hint for this type, don't add another
|
||||
if (AdaptionHints.Any(h => h is DeviceAdaptionHint d && d.DeviceType == device.DeviceType))
|
||||
continue;
|
||||
if (DoesLayerCoverDevice(device))
|
||||
{
|
||||
DeviceAdaptionHint hint = new() {DeviceType = device.DeviceType};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
|
||||
// Any fully covered category will add a category adaption hint for its category
|
||||
foreach (DeviceCategory deviceCategory in Enum.GetValues<DeviceCategory>())
|
||||
{
|
||||
if (AdaptionHints.Any(h => h is CategoryAdaptionHint c && c.Category == deviceCategory))
|
||||
continue;
|
||||
|
||||
List<ArtemisDevice> categoryDevices = devices.Where(d => d.Categories.Contains(deviceCategory)).ToList();
|
||||
if (categoryDevices.Any() && categoryDevices.All(DoesLayerCoverDevice))
|
||||
{
|
||||
CategoryAdaptionHint hint = new() {Category = deviceCategory};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newHints;
|
||||
}
|
||||
|
||||
private bool DoesLayerCoverDevice(ArtemisDevice device)
|
||||
{
|
||||
return device.Leds.All(l => Layer.Leds.Contains(l));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an adaption hint to the adapter.
|
||||
/// </summary>
|
||||
/// <param name="adaptionHint">The adaption hint to add.</param>
|
||||
public void Add(IAdaptionHint adaptionHint)
|
||||
{
|
||||
if (_adaptionHints.Contains(adaptionHint))
|
||||
return;
|
||||
|
||||
_adaptionHints.Add(adaptionHint);
|
||||
AdapterHintAdded?.Invoke(this, new LayerAdapterHintEventArgs(adaptionHint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific adaption hint from the adapter.
|
||||
/// </summary>
|
||||
/// <param name="adaptionHint">The adaption hint to remove.</param>
|
||||
public void Remove(IAdaptionHint adaptionHint)
|
||||
{
|
||||
if (_adaptionHints.Remove(adaptionHint))
|
||||
AdapterHintRemoved?.Invoke(this, new LayerAdapterHintEventArgs(adaptionHint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all adaption hints from the adapter.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
while (_adaptionHints.Any())
|
||||
Remove(_adaptionHints.First());
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
_adaptionHints.Clear();
|
||||
// Kind of meh.
|
||||
// This leaves the adapter responsible for finding the right hint for the right entity, but it's gotta be done somewhere..
|
||||
foreach (IAdaptionHintEntity hintEntity in Layer.LayerEntity.AdaptionHints)
|
||||
switch (hintEntity)
|
||||
{
|
||||
case DeviceAdaptionHintEntity entity:
|
||||
Add(new DeviceAdaptionHint(entity));
|
||||
break;
|
||||
case CategoryAdaptionHintEntity entity:
|
||||
Add(new CategoryAdaptionHint(entity));
|
||||
break;
|
||||
case KeyboardSectionAdaptionHintEntity entity:
|
||||
Add(new KeyboardSectionAdaptionHint(entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Layer.LayerEntity.AdaptionHints.Clear();
|
||||
foreach (IAdaptionHint adaptionHint in AdaptionHints)
|
||||
Layer.LayerEntity.AdaptionHints.Add(adaptionHint.GetEntry());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a new adapter hint is added to the adapter.
|
||||
/// </summary>
|
||||
public event EventHandler<LayerAdapterHintEventArgs>? AdapterHintAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever an adapter hint is removed from the adapter.
|
||||
/// </summary>
|
||||
public event EventHandler<LayerAdapterHintEventArgs>? AdapterHintRemoved;
|
||||
_adaptionHints = new List<IAdaptionHint>();
|
||||
Layer = layer;
|
||||
AdaptionHints = new ReadOnlyCollection<IAdaptionHint>(_adaptionHints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer this adapter can adapt
|
||||
/// </summary>
|
||||
public Layer Layer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list containing the adaption hints used by this adapter
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<IAdaptionHint> AdaptionHints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the layer, adapting it to the provided <paramref name="devices" />
|
||||
/// </summary>
|
||||
/// <param name="devices">The devices to adapt the layer to</param>
|
||||
public void Adapt(List<ArtemisDevice> devices)
|
||||
{
|
||||
// Use adaption hints if provided
|
||||
if (AdaptionHints.Any())
|
||||
{
|
||||
foreach (IAdaptionHint adaptionHint in AdaptionHints)
|
||||
adaptionHint.Apply(Layer, devices);
|
||||
}
|
||||
// If there are no hints, try to find matching LEDs anyway
|
||||
else
|
||||
{
|
||||
List<ArtemisLed> availableLeds = devices.SelectMany(d => d.Leds).ToList();
|
||||
List<ArtemisLed> usedLeds = new();
|
||||
|
||||
foreach (LedEntity ledEntity in Layer.LayerEntity.Leds)
|
||||
{
|
||||
// TODO: If this is a keyboard LED and the layouts don't match, convert it before looking for it on the devices
|
||||
|
||||
LedId ledId = Enum.Parse<LedId>(ledEntity.LedName);
|
||||
ArtemisLed? led = availableLeds.FirstOrDefault(l => l.RgbLed.Id == ledId);
|
||||
|
||||
if (led != null)
|
||||
{
|
||||
availableLeds.Remove(led);
|
||||
usedLeds.Add(led);
|
||||
}
|
||||
}
|
||||
|
||||
Layer.AddLeds(usedLeds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically determine hints for this layer
|
||||
/// </summary>
|
||||
public List<IAdaptionHint> DetermineHints(IEnumerable<ArtemisDevice> devices)
|
||||
{
|
||||
List<IAdaptionHint> newHints = new();
|
||||
if (devices.All(DoesLayerCoverDevice))
|
||||
{
|
||||
DeviceAdaptionHint hint = new() {DeviceType = RGBDeviceType.All};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any fully covered device will add a device adaption hint for that type
|
||||
foreach (IGrouping<ArtemisDevice, ArtemisLed> deviceLeds in Layer.Leds.GroupBy(l => l.Device))
|
||||
{
|
||||
ArtemisDevice device = deviceLeds.Key;
|
||||
// If there is already an adaption hint for this type, don't add another
|
||||
if (AdaptionHints.Any(h => h is DeviceAdaptionHint d && d.DeviceType == device.DeviceType))
|
||||
continue;
|
||||
if (DoesLayerCoverDevice(device))
|
||||
{
|
||||
DeviceAdaptionHint hint = new() {DeviceType = device.DeviceType};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
|
||||
// Any fully covered category will add a category adaption hint for its category
|
||||
foreach (DeviceCategory deviceCategory in Enum.GetValues<DeviceCategory>())
|
||||
{
|
||||
if (AdaptionHints.Any(h => h is CategoryAdaptionHint c && c.Category == deviceCategory))
|
||||
continue;
|
||||
|
||||
List<ArtemisDevice> categoryDevices = devices.Where(d => d.Categories.Contains(deviceCategory)).ToList();
|
||||
if (categoryDevices.Any() && categoryDevices.All(DoesLayerCoverDevice))
|
||||
{
|
||||
CategoryAdaptionHint hint = new() {Category = deviceCategory};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newHints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an adaption hint to the adapter.
|
||||
/// </summary>
|
||||
/// <param name="adaptionHint">The adaption hint to add.</param>
|
||||
public void Add(IAdaptionHint adaptionHint)
|
||||
{
|
||||
if (_adaptionHints.Contains(adaptionHint))
|
||||
return;
|
||||
|
||||
_adaptionHints.Add(adaptionHint);
|
||||
AdapterHintAdded?.Invoke(this, new LayerAdapterHintEventArgs(adaptionHint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific adaption hint from the adapter.
|
||||
/// </summary>
|
||||
/// <param name="adaptionHint">The adaption hint to remove.</param>
|
||||
public void Remove(IAdaptionHint adaptionHint)
|
||||
{
|
||||
if (_adaptionHints.Remove(adaptionHint))
|
||||
AdapterHintRemoved?.Invoke(this, new LayerAdapterHintEventArgs(adaptionHint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all adaption hints from the adapter.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
while (_adaptionHints.Any())
|
||||
Remove(_adaptionHints.First());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a new adapter hint is added to the adapter.
|
||||
/// </summary>
|
||||
public event EventHandler<LayerAdapterHintEventArgs>? AdapterHintAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever an adapter hint is removed from the adapter.
|
||||
/// </summary>
|
||||
public event EventHandler<LayerAdapterHintEventArgs>? AdapterHintRemoved;
|
||||
|
||||
private bool DoesLayerCoverDevice(ArtemisDevice device)
|
||||
{
|
||||
return device.Leds.All(l => Layer.Leds.Contains(l));
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
_adaptionHints.Clear();
|
||||
// Kind of meh.
|
||||
// This leaves the adapter responsible for finding the right hint for the right entity, but it's gotta be done somewhere..
|
||||
foreach (IAdaptionHintEntity hintEntity in Layer.LayerEntity.AdaptionHints)
|
||||
{
|
||||
switch (hintEntity)
|
||||
{
|
||||
case DeviceAdaptionHintEntity entity:
|
||||
Add(new DeviceAdaptionHint(entity));
|
||||
break;
|
||||
case CategoryAdaptionHintEntity entity:
|
||||
Add(new CategoryAdaptionHint(entity));
|
||||
break;
|
||||
case KeyboardSectionAdaptionHintEntity entity:
|
||||
Add(new KeyboardSectionAdaptionHint(entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Layer.LayerEntity.AdaptionHints.Clear();
|
||||
foreach (IAdaptionHint adaptionHint in AdaptionHints)
|
||||
Layer.LayerEntity.AdaptionHints.Add(adaptionHint.GetEntry());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,37 +1,36 @@
|
||||
using Artemis.Core.LayerBrushes;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A reference to a <see cref="LayerBrushDescriptor" />
|
||||
/// </summary>
|
||||
public class LayerBrushReference
|
||||
{
|
||||
/// <summary>
|
||||
/// A reference to a <see cref="LayerBrushDescriptor" />
|
||||
/// Creates a new instance of the <see cref="LayerBrushReference" /> class
|
||||
/// </summary>
|
||||
public class LayerBrushReference
|
||||
public LayerBrushReference()
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="LayerBrushReference" /> class
|
||||
/// </summary>
|
||||
public LayerBrushReference()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="LayerBrushReference" /> class
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The descriptor to point the new reference at</param>
|
||||
public LayerBrushReference(LayerBrushDescriptor descriptor)
|
||||
{
|
||||
LayerBrushProviderId = descriptor.Provider.Id;
|
||||
BrushType = descriptor.LayerBrushType.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the layer brush provided the brush was provided by
|
||||
/// </summary>
|
||||
public string? LayerBrushProviderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full type name of the brush descriptor
|
||||
/// </summary>
|
||||
public string? BrushType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="LayerBrushReference" /> class
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The descriptor to point the new reference at</param>
|
||||
public LayerBrushReference(LayerBrushDescriptor descriptor)
|
||||
{
|
||||
LayerBrushProviderId = descriptor.Provider.Id;
|
||||
BrushType = descriptor.LayerBrushType.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the layer brush provided the brush was provided by
|
||||
/// </summary>
|
||||
public string? LayerBrushProviderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full type name of the brush descriptor
|
||||
/// </summary>
|
||||
public string? BrushType { get; set; }
|
||||
}
|
||||
@ -1,25 +1,24 @@
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a property group on a layer
|
||||
/// <para>
|
||||
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class LayerEffectPropertyGroup : LayerPropertyGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a property group on a layer
|
||||
/// <para>
|
||||
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// Whether or not this layer effect is enabled
|
||||
/// </summary>
|
||||
public abstract class LayerEffectPropertyGroup : LayerPropertyGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not this layer effect is enabled
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Enabled", Description = "Whether or not this layer effect is enabled")]
|
||||
public BoolLayerProperty IsEnabled { get; set; } = null!;
|
||||
[PropertyDescription(Name = "Enabled", Description = "Whether or not this layer effect is enabled")]
|
||||
public BoolLayerProperty IsEnabled { get; set; } = null!;
|
||||
|
||||
internal void InitializeIsEnabled()
|
||||
{
|
||||
IsEnabled.DefaultValue = true;
|
||||
if (!IsEnabled.IsLoadedFromStorage)
|
||||
IsEnabled.SetCurrentValue(true);
|
||||
}
|
||||
internal void InitializeIsEnabled()
|
||||
{
|
||||
IsEnabled.DefaultValue = true;
|
||||
if (!IsEnabled.IsLoadedFromStorage)
|
||||
IsEnabled.SetCurrentValue(true);
|
||||
}
|
||||
}
|
||||
@ -2,52 +2,51 @@
|
||||
|
||||
#pragma warning disable 8618
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the general properties of a layer
|
||||
/// </summary>
|
||||
public class LayerGeneralProperties : LayerPropertyGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the general properties of a layer
|
||||
/// The type of brush to use for this layer
|
||||
/// </summary>
|
||||
public class LayerGeneralProperties : LayerPropertyGroup
|
||||
[PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")]
|
||||
public LayerBrushReferenceLayerProperty BrushReference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of shape to draw in this layer
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
|
||||
public EnumLayerProperty<LayerShapeType> ShapeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How to blend this layer into the resulting image
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")]
|
||||
public EnumLayerProperty<SKBlendMode> BlendMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How the transformation properties are applied to the layer
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Transform mode", Description = "How the transformation properties are applied to the layer")]
|
||||
public EnumLayerProperty<LayerTransformMode> TransformMode { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PopulateDefaults()
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of brush to use for this layer
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")]
|
||||
public LayerBrushReferenceLayerProperty BrushReference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of shape to draw in this layer
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
|
||||
public EnumLayerProperty<LayerShapeType> ShapeType { get; set; }
|
||||
ShapeType.DefaultValue = LayerShapeType.Rectangle;
|
||||
BlendMode.DefaultValue = SKBlendMode.SrcOver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How to blend this layer into the resulting image
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")]
|
||||
public EnumLayerProperty<SKBlendMode> BlendMode { get; set; }
|
||||
/// <inheritdoc />
|
||||
protected override void EnableProperties()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How the transformation properties are applied to the layer
|
||||
/// </summary>
|
||||
[PropertyDescription(Name = "Transform mode", Description = "How the transformation properties are applied to the layer")]
|
||||
public EnumLayerProperty<LayerTransformMode> TransformMode { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PopulateDefaults()
|
||||
{
|
||||
ShapeType.DefaultValue = LayerShapeType.Rectangle;
|
||||
BlendMode.DefaultValue = SKBlendMode.SrcOver;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void EnableProperties()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisableProperties()
|
||||
{
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void DisableProperties()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an attribute that marks a layer property to be ignored
|
||||
/// </summary>
|
||||
public class LayerPropertyIgnoreAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an attribute that marks a layer property to be ignored
|
||||
/// </summary>
|
||||
public class LayerPropertyIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,55 +1,54 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a description attribute used to decorate layer properties
|
||||
/// </summary>
|
||||
public class PropertyDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a description attribute used to decorate layer properties
|
||||
/// The identifier of this property used for storage, if not set one will be generated property name in code
|
||||
/// </summary>
|
||||
public class PropertyDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier of this property used for storage, if not set one will be generated property name in code
|
||||
/// </summary>
|
||||
public string? Identifier { get; set; }
|
||||
public string? Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly name for this property, shown in the UI
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
/// <summary>
|
||||
/// The user-friendly name for this property, shown in the UI
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly description for this property, shown in the UI
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
/// <summary>
|
||||
/// The user-friendly description for this property, shown in the UI
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input prefix to show before input elements in the UI
|
||||
/// </summary>
|
||||
public string? InputPrefix { get; set; }
|
||||
/// <summary>
|
||||
/// Input prefix to show before input elements in the UI
|
||||
/// </summary>
|
||||
public string? InputPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input affix to show behind input elements in the UI
|
||||
/// </summary>
|
||||
public string? InputAffix { get; set; }
|
||||
/// <summary>
|
||||
/// Input affix to show behind input elements in the UI
|
||||
/// </summary>
|
||||
public string? InputAffix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The input drag step size, used in the UI
|
||||
/// </summary>
|
||||
public float InputStepSize { get; set; }
|
||||
/// <summary>
|
||||
/// The input drag step size, used in the UI
|
||||
/// </summary>
|
||||
public float InputStepSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum input value, only enforced in the UI
|
||||
/// </summary>
|
||||
public object? MinInputValue { get; set; }
|
||||
/// <summary>
|
||||
/// Minimum input value, only enforced in the UI
|
||||
/// </summary>
|
||||
public object? MinInputValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum input value, only enforced in the UI
|
||||
/// </summary>
|
||||
public object? MaxInputValue { get; set; }
|
||||
/// <summary>
|
||||
/// Maximum input value, only enforced in the UI
|
||||
/// </summary>
|
||||
public object? MaxInputValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not keyframes are always disabled
|
||||
/// </summary>
|
||||
public bool DisableKeyframes { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Whether or not keyframes are always disabled
|
||||
/// </summary>
|
||||
public bool DisableKeyframes { get; set; }
|
||||
}
|
||||
@ -1,25 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a description attribute used to decorate layer property groups
|
||||
/// </summary>
|
||||
public class PropertyGroupDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a description attribute used to decorate layer property groups
|
||||
/// The identifier of this property group used for storage, if not set one will be generated based on the group name in
|
||||
/// code
|
||||
/// </summary>
|
||||
public class PropertyGroupDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier of this property group used for storage, if not set one will be generated based on the group name in code
|
||||
/// </summary>
|
||||
public string? Identifier { get; set; }
|
||||
public string? Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly name for this property group, shown in the UI.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly description for this property group, shown in the UI.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// The user-friendly name for this property group, shown in the UI.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly description for this property group, shown in the UI.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
@ -1,63 +1,62 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a range between two single-precision floating point numbers
|
||||
/// </summary>
|
||||
public readonly struct FloatRange
|
||||
{
|
||||
private readonly Random _rand;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a range between two single-precision floating point numbers
|
||||
/// Creates a new instance of the <see cref="FloatRange" /> class
|
||||
/// </summary>
|
||||
public readonly struct FloatRange
|
||||
/// <param name="start">The start value of the range</param>
|
||||
/// <param name="end">The end value of the range</param>
|
||||
public FloatRange(float start, float end)
|
||||
{
|
||||
private readonly Random _rand;
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="FloatRange" /> class
|
||||
/// </summary>
|
||||
/// <param name="start">The start value of the range</param>
|
||||
/// <param name="end">The end value of the range</param>
|
||||
public FloatRange(float start, float end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
_rand = new Random();
|
||||
}
|
||||
|
||||
_rand = new Random();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the start value of the range
|
||||
/// </summary>
|
||||
public float Start { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start value of the range
|
||||
/// </summary>
|
||||
public float Start { get; }
|
||||
/// <summary>
|
||||
/// Gets the end value of the range
|
||||
/// </summary>
|
||||
public float End { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end value of the range
|
||||
/// </summary>
|
||||
public float End { get; }
|
||||
/// <summary>
|
||||
/// Determines whether the given value is in this range
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <param name="inclusive">
|
||||
/// Whether the value may be equal to <see cref="Start" /> or <see cref="End" />
|
||||
/// <para>Defaults to <see langword="true" /></para>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public bool IsInRange(float value, bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return value >= Start && value <= End;
|
||||
return value > Start && value < End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given value is in this range
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <param name="inclusive">
|
||||
/// Whether the value may be equal to <see cref="Start" /> or <see cref="End" />
|
||||
/// <para>Defaults to <see langword="true" /></para>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public bool IsInRange(float value, bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return value >= Start && value <= End;
|
||||
return value > Start && value < End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pseudo-random value between <see cref="Start" /> and <see cref="End" />
|
||||
/// </summary>
|
||||
/// <param name="inclusive">Whether the value may be equal to <see cref="Start" /></param>
|
||||
/// <returns>The pseudo-random value</returns>
|
||||
public float GetRandomValue(bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return _rand.Next((int) (Start * 100), (int) (End * 100)) / 100f;
|
||||
return _rand.Next((int) (Start * 100) + 1, (int) (End * 100)) / 100f;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a pseudo-random value between <see cref="Start" /> and <see cref="End" />
|
||||
/// </summary>
|
||||
/// <param name="inclusive">Whether the value may be equal to <see cref="Start" /></param>
|
||||
/// <returns>The pseudo-random value</returns>
|
||||
public float GetRandomValue(bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return _rand.Next((int) (Start * 100), (int) (End * 100)) / 100f;
|
||||
return _rand.Next((int) (Start * 100) + 1, (int) (End * 100)) / 100f;
|
||||
}
|
||||
}
|
||||
@ -2,152 +2,151 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI.
|
||||
/// <para>
|
||||
/// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface ILayerProperty : IStorageModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI.
|
||||
/// Gets the description attribute applied to this property
|
||||
/// </summary>
|
||||
PropertyDescriptionAttribute PropertyDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element (such as layer or folder) this property is applied to
|
||||
/// </summary>
|
||||
RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this layer property, set after construction
|
||||
/// </summary>
|
||||
LayerPropertyGroup LayerPropertyGroup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding of this property
|
||||
/// </summary>
|
||||
IDataBinding BaseDataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the layer has any data binding properties
|
||||
/// </summary>
|
||||
public bool HasDataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether data bindings are supported on this type of property
|
||||
/// </summary>
|
||||
public bool DataBindingsSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique path of the property on the render element
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all the keyframes on this layer property
|
||||
/// </summary>
|
||||
ReadOnlyCollection<ILayerPropertyKeyframe> UntypedKeyframes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property
|
||||
/// </summary>
|
||||
Type PropertyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
|
||||
/// </summary>
|
||||
bool IsLoadedFromStorage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the layer property
|
||||
/// <para>
|
||||
/// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// Note: This isn't done in the constructor to keep it parameterless which is easier for implementations of
|
||||
/// <see cref="LayerProperty{T}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface ILayerProperty : IStorageModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description attribute applied to this property
|
||||
/// </summary>
|
||||
PropertyDescriptionAttribute PropertyDescription { get; }
|
||||
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element (such as layer or folder) this property is applied to
|
||||
/// </summary>
|
||||
RenderProfileElement ProfileElement { get; }
|
||||
/// <summary>
|
||||
/// Attempts to create a keyframe for this property from the provided entity
|
||||
/// </summary>
|
||||
/// <param name="keyframeEntity">The entity representing the keyframe to create</param>
|
||||
/// <returns>If succeeded the resulting keyframe, otherwise <see langword="null" /></returns>
|
||||
ILayerPropertyKeyframe? CreateKeyframeFromEntity(KeyframeEntity keyframeEntity);
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this layer property, set after construction
|
||||
/// </summary>
|
||||
LayerPropertyGroup LayerPropertyGroup { get; }
|
||||
/// <summary>
|
||||
/// Overrides the property value with the default value
|
||||
/// </summary>
|
||||
void ApplyDefaultValue();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding of this property
|
||||
/// </summary>
|
||||
IDataBinding BaseDataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the layer has any data binding properties
|
||||
/// </summary>
|
||||
public bool HasDataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether data bindings are supported on this type of property
|
||||
/// </summary>
|
||||
public bool DataBindingsSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique path of the property on the render element
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all the keyframes on this layer property
|
||||
/// </summary>
|
||||
ReadOnlyCollection<ILayerPropertyKeyframe> UntypedKeyframes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property
|
||||
/// </summary>
|
||||
Type PropertyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
|
||||
/// </summary>
|
||||
bool IsLoadedFromStorage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the layer property
|
||||
/// <para>
|
||||
/// Note: This isn't done in the constructor to keep it parameterless which is easier for implementations of
|
||||
/// <see cref="LayerProperty{T}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create a keyframe for this property from the provided entity
|
||||
/// </summary>
|
||||
/// <param name="keyframeEntity">The entity representing the keyframe to create</param>
|
||||
/// <returns>If succeeded the resulting keyframe, otherwise <see langword="null" /></returns>
|
||||
ILayerPropertyKeyframe? CreateKeyframeFromEntity(KeyframeEntity keyframeEntity);
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the property value with the default value
|
||||
/// </summary>
|
||||
void ApplyDefaultValue();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the layer properties internal state
|
||||
/// </summary>
|
||||
/// <param name="timeline">The timeline to apply to the property</param>
|
||||
void Update(Timeline timeline);
|
||||
/// <summary>
|
||||
/// Updates the layer properties internal state
|
||||
/// </summary>
|
||||
/// <param name="timeline">The timeline to apply to the property</param>
|
||||
void Update(Timeline timeline);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates just the data binding instead of the entire layer
|
||||
/// </summary>
|
||||
void UpdateDataBinding();
|
||||
/// <summary>
|
||||
/// Updates just the data binding instead of the entire layer
|
||||
/// </summary>
|
||||
void UpdateDataBinding();
|
||||
|
||||
/// <summary>
|
||||
/// Removes a keyframe from the layer property without knowing it's type.
|
||||
/// <para>Prefer <see cref="LayerProperty{T}.RemoveKeyframe"/>.</para>
|
||||
/// </summary>
|
||||
/// <param name="keyframe"></param>
|
||||
void RemoveUntypedKeyframe(ILayerPropertyKeyframe keyframe);
|
||||
/// <summary>
|
||||
/// Removes a keyframe from the layer property without knowing it's type.
|
||||
/// <para>Prefer <see cref="LayerProperty{T}.RemoveKeyframe" />.</para>
|
||||
/// </summary>
|
||||
/// <param name="keyframe"></param>
|
||||
void RemoveUntypedKeyframe(ILayerPropertyKeyframe keyframe);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a keyframe to the layer property without knowing it's type.
|
||||
/// <para>Prefer <see cref="LayerProperty{T}.AddKeyframe"/>.</para>
|
||||
/// </summary>
|
||||
/// <param name="keyframe"></param>
|
||||
void AddUntypedKeyframe(ILayerPropertyKeyframe keyframe);
|
||||
/// <summary>
|
||||
/// Adds a keyframe to the layer property without knowing it's type.
|
||||
/// <para>Prefer <see cref="LayerProperty{T}.AddKeyframe" />.</para>
|
||||
/// </summary>
|
||||
/// <param name="keyframe"></param>
|
||||
void AddUntypedKeyframe(ILayerPropertyKeyframe keyframe);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the layer property is disposed
|
||||
/// </summary>
|
||||
public event EventHandler Disposed;
|
||||
/// <summary>
|
||||
/// Occurs when the layer property is disposed
|
||||
/// </summary>
|
||||
public event EventHandler Disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs once every frame when the layer property is updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? Updated;
|
||||
/// <summary>
|
||||
/// Occurs once every frame when the layer property is updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? Updated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the current value of the layer property was updated by some form of input
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
|
||||
/// <summary>
|
||||
/// Occurs when the current value of the layer property was updated by some form of input
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the visibility value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
|
||||
/// <summary>
|
||||
/// Occurs when the visibility value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when keyframes are enabled/disabled
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
|
||||
/// <summary>
|
||||
/// Occurs when keyframes are enabled/disabled
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new keyframe was added to the layer property
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeAdded;
|
||||
/// <summary>
|
||||
/// Occurs when a new keyframe was added to the layer property
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a keyframe was removed from the layer property
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeRemoved;
|
||||
}
|
||||
/// <summary>
|
||||
/// Occurs when a keyframe was removed from the layer property
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeRemoved;
|
||||
}
|
||||
@ -2,43 +2,42 @@
|
||||
using System.ComponentModel;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a keyframe on a <see cref="ILayerProperty" /> containing a value and a timestamp
|
||||
/// </summary>
|
||||
public interface ILayerPropertyKeyframe : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a keyframe on a <see cref="ILayerProperty" /> containing a value and a timestamp
|
||||
/// Gets an untyped reference to the layer property of this keyframe
|
||||
/// </summary>
|
||||
public interface ILayerPropertyKeyframe : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an untyped reference to the layer property of this keyframe
|
||||
/// </summary>
|
||||
ILayerProperty UntypedLayerProperty { get; }
|
||||
ILayerProperty UntypedLayerProperty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of this keyframe in the timeline
|
||||
/// </summary>
|
||||
TimeSpan Position { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the position of this keyframe in the timeline
|
||||
/// </summary>
|
||||
TimeSpan Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the easing function applied on the value of the keyframe
|
||||
/// </summary>
|
||||
Easings.Functions EasingFunction { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the easing function applied on the value of the keyframe
|
||||
/// </summary>
|
||||
Easings.Functions EasingFunction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity this keyframe uses for persistent storage
|
||||
/// </summary>
|
||||
KeyframeEntity GetKeyframeEntity();
|
||||
/// <summary>
|
||||
/// Gets the entity this keyframe uses for persistent storage
|
||||
/// </summary>
|
||||
KeyframeEntity GetKeyframeEntity();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the keyframe from the layer property
|
||||
/// </summary>
|
||||
void Remove();
|
||||
/// <summary>
|
||||
/// Removes the keyframe from the layer property
|
||||
/// </summary>
|
||||
void Remove();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of this keyframe.
|
||||
/// <para>Note: The copied keyframe is not added to the layer property.</para>
|
||||
/// </summary>
|
||||
/// <returns>The resulting copy</returns>
|
||||
ILayerPropertyKeyframe CreateCopy();
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a copy of this keyframe.
|
||||
/// <para>Note: The copied keyframe is not added to the layer property.</para>
|
||||
/// </summary>
|
||||
/// <returns>The resulting copy</returns>
|
||||
ILayerPropertyKeyframe CreateCopy();
|
||||
}
|
||||
@ -1,63 +1,62 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a range between two signed integers
|
||||
/// </summary>
|
||||
public readonly struct IntRange
|
||||
{
|
||||
private readonly Random _rand;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a range between two signed integers
|
||||
/// Creates a new instance of the <see cref="IntRange" /> class
|
||||
/// </summary>
|
||||
public readonly struct IntRange
|
||||
/// <param name="start">The start value of the range</param>
|
||||
/// <param name="end">The end value of the range</param>
|
||||
public IntRange(int start, int end)
|
||||
{
|
||||
private readonly Random _rand;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="IntRange" /> class
|
||||
/// </summary>
|
||||
/// <param name="start">The start value of the range</param>
|
||||
/// <param name="end">The end value of the range</param>
|
||||
public IntRange(int start, int end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
_rand = new Random();
|
||||
}
|
||||
_rand = new Random();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start value of the range
|
||||
/// </summary>
|
||||
public int Start { get; }
|
||||
/// <summary>
|
||||
/// Gets the start value of the range
|
||||
/// </summary>
|
||||
public int Start { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end value of the range
|
||||
/// </summary>
|
||||
public int End { get; }
|
||||
/// <summary>
|
||||
/// Gets the end value of the range
|
||||
/// </summary>
|
||||
public int End { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given value is in this range
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <param name="inclusive">
|
||||
/// Whether the value may be equal to <see cref="Start" /> or <see cref="End" />
|
||||
/// <para>Defaults to <see langword="true" /></para>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public bool IsInRange(int value, bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return value >= Start && value <= End;
|
||||
return value > Start && value < End;
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines whether the given value is in this range
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <param name="inclusive">
|
||||
/// Whether the value may be equal to <see cref="Start" /> or <see cref="End" />
|
||||
/// <para>Defaults to <see langword="true" /></para>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public bool IsInRange(int value, bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return value >= Start && value <= End;
|
||||
return value > Start && value < End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pseudo-random value between <see cref="Start" /> and <see cref="End" />
|
||||
/// </summary>
|
||||
/// <param name="inclusive">Whether the value may be equal to <see cref="Start" /></param>
|
||||
/// <returns>The pseudo-random value</returns>
|
||||
public int GetRandomValue(bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return _rand.Next(Start, End + 1);
|
||||
return _rand.Next(Start + 1, End);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a pseudo-random value between <see cref="Start" /> and <see cref="End" />
|
||||
/// </summary>
|
||||
/// <param name="inclusive">Whether the value may be equal to <see cref="Start" /></param>
|
||||
/// <returns>The pseudo-random value</returns>
|
||||
public int GetRandomValue(bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
return _rand.Next(Start, End + 1);
|
||||
return _rand.Next(Start + 1, End);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,90 +1,89 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a keyframe on a <see cref="LayerProperty{T}" /> containing a value and a timestamp
|
||||
/// </summary>
|
||||
public class LayerPropertyKeyframe<T> : CorePropertyChanged, ILayerPropertyKeyframe
|
||||
{
|
||||
private LayerProperty<T> _layerProperty;
|
||||
private TimeSpan _position;
|
||||
private T _value;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a keyframe on a <see cref="LayerProperty{T}" /> containing a value and a timestamp
|
||||
/// Creates a new instance of the <see cref="LayerPropertyKeyframe{T}" /> class
|
||||
/// </summary>
|
||||
public class LayerPropertyKeyframe<T> : CorePropertyChanged, ILayerPropertyKeyframe
|
||||
/// <param name="value">The value of the keyframe</param>
|
||||
/// <param name="position">The position of this keyframe in the timeline</param>
|
||||
/// <param name="easingFunction">The easing function applied on the value of the keyframe</param>
|
||||
/// <param name="layerProperty">The layer property this keyframe is applied to</param>
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty)
|
||||
{
|
||||
private LayerProperty<T> _layerProperty;
|
||||
private TimeSpan _position;
|
||||
private T _value;
|
||||
_position = position;
|
||||
_layerProperty = layerProperty;
|
||||
_value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="LayerPropertyKeyframe{T}" /> class
|
||||
/// </summary>
|
||||
/// <param name="value">The value of the keyframe</param>
|
||||
/// <param name="position">The position of this keyframe in the timeline</param>
|
||||
/// <param name="easingFunction">The easing function applied on the value of the keyframe</param>
|
||||
/// <param name="layerProperty">The layer property this keyframe is applied to</param>
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty)
|
||||
EasingFunction = easingFunction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layer property this keyframe is applied to
|
||||
/// </summary>
|
||||
public LayerProperty<T> LayerProperty
|
||||
{
|
||||
get => _layerProperty;
|
||||
internal set => SetAndNotify(ref _layerProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of this keyframe
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetAndNotify(ref _value, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayerProperty UntypedLayerProperty => LayerProperty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
_position = position;
|
||||
_layerProperty = layerProperty;
|
||||
_value = value;
|
||||
|
||||
EasingFunction = easingFunction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layer property this keyframe is applied to
|
||||
/// </summary>
|
||||
public LayerProperty<T> LayerProperty
|
||||
{
|
||||
get => _layerProperty;
|
||||
internal set => SetAndNotify(ref _layerProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of this keyframe
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetAndNotify(ref _value, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayerProperty UntypedLayerProperty => LayerProperty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _position, value);
|
||||
LayerProperty.SortKeyframes();
|
||||
LayerProperty.ReapplyUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public KeyframeEntity GetKeyframeEntity()
|
||||
{
|
||||
return new KeyframeEntity
|
||||
{
|
||||
Value = CoreJson.SerializeObject(Value),
|
||||
Position = Position,
|
||||
EasingFunction = (int) EasingFunction
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove()
|
||||
{
|
||||
LayerProperty.RemoveKeyframe(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayerPropertyKeyframe CreateCopy()
|
||||
{
|
||||
return new LayerPropertyKeyframe<T>(Value, Position, EasingFunction, LayerProperty);
|
||||
SetAndNotify(ref _position, value);
|
||||
LayerProperty.SortKeyframes();
|
||||
LayerProperty.ReapplyUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public KeyframeEntity GetKeyframeEntity()
|
||||
{
|
||||
return new KeyframeEntity
|
||||
{
|
||||
Value = CoreJson.SerializeObject(Value),
|
||||
Position = Position,
|
||||
EasingFunction = (int) EasingFunction
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove()
|
||||
{
|
||||
LayerProperty.RemoveKeyframe(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayerPropertyKeyframe CreateCopy()
|
||||
{
|
||||
return new LayerPropertyKeyframe<T>(Value, Position, EasingFunction, LayerProperty);
|
||||
}
|
||||
}
|
||||
@ -79,7 +79,8 @@ public sealed class LayerPropertyPreview<T> : IDisposable
|
||||
}
|
||||
|
||||
Property.SetCurrentValue(OriginalValue, Time);
|
||||
return !Equals(OriginalValue, PreviewValue); ;
|
||||
return !Equals(OriginalValue, PreviewValue);
|
||||
;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -3,346 +3,343 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a property group on a layer
|
||||
/// <para>
|
||||
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class LayerPropertyGroup : IDisposable
|
||||
{
|
||||
private readonly List<ILayerProperty> _layerProperties;
|
||||
private readonly List<LayerPropertyGroup> _layerPropertyGroups;
|
||||
private bool _disposed;
|
||||
private bool _isHidden;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a property group on a layer
|
||||
/// <para>
|
||||
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// A base constructor for a <see cref="LayerPropertyGroup" />
|
||||
/// </summary>
|
||||
public abstract class LayerPropertyGroup : IDisposable
|
||||
protected LayerPropertyGroup()
|
||||
{
|
||||
private readonly List<ILayerProperty> _layerProperties;
|
||||
private readonly List<LayerPropertyGroup> _layerPropertyGroups;
|
||||
private bool _disposed;
|
||||
private bool _isHidden;
|
||||
// These are set right after construction to keep the constructor (and inherited constructs) clean
|
||||
ProfileElement = null!;
|
||||
GroupDescription = null!;
|
||||
Path = "";
|
||||
|
||||
/// <summary>
|
||||
/// A base constructor for a <see cref="LayerPropertyGroup" />
|
||||
/// </summary>
|
||||
protected LayerPropertyGroup()
|
||||
_layerProperties = new List<ILayerProperty>();
|
||||
_layerPropertyGroups = new List<LayerPropertyGroup>();
|
||||
|
||||
LayerProperties = new ReadOnlyCollection<ILayerProperty>(_layerProperties);
|
||||
LayerPropertyGroups = new ReadOnlyCollection<LayerPropertyGroup>(_layerPropertyGroups);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element (such as layer or folder) this group is associated with
|
||||
/// </summary>
|
||||
public RenderProfileElement ProfileElement { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of this group
|
||||
/// </summary>
|
||||
public PropertyGroupDescriptionAttribute GroupDescription { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this group
|
||||
/// </summary>
|
||||
[LayerPropertyIgnore] // Ignore the parent when selecting child groups
|
||||
public LayerPropertyGroup? Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique path of the property on the render element
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this property groups properties are all initialized
|
||||
/// </summary>
|
||||
public bool PropertiesInitialized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden
|
||||
{
|
||||
get => _isHidden;
|
||||
set
|
||||
{
|
||||
// These are set right after construction to keep the constructor (and inherited constructs) clean
|
||||
ProfileElement = null!;
|
||||
GroupDescription = null!;
|
||||
Path = "";
|
||||
|
||||
_layerProperties = new List<ILayerProperty>();
|
||||
_layerPropertyGroups = new List<LayerPropertyGroup>();
|
||||
|
||||
LayerProperties = new ReadOnlyCollection<ILayerProperty>(_layerProperties);
|
||||
LayerPropertyGroups = new ReadOnlyCollection<LayerPropertyGroup>(_layerPropertyGroups);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element (such as layer or folder) this group is associated with
|
||||
/// </summary>
|
||||
public RenderProfileElement ProfileElement { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of this group
|
||||
/// </summary>
|
||||
public PropertyGroupDescriptionAttribute GroupDescription { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this group
|
||||
/// </summary>
|
||||
[LayerPropertyIgnore] // Ignore the parent when selecting child groups
|
||||
public LayerPropertyGroup? Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique path of the property on the render element
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this property groups properties are all initialized
|
||||
/// </summary>
|
||||
public bool PropertiesInitialized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden
|
||||
{
|
||||
get => _isHidden;
|
||||
set
|
||||
{
|
||||
_isHidden = value;
|
||||
OnVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity this property group uses for persistent storage
|
||||
/// </summary>
|
||||
public PropertyGroupEntity? PropertyGroupEntity { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all layer properties in this group
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ILayerProperty> LayerProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of al child groups in this group
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Recursively gets all layer properties on this group and any subgroups
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ILayerProperty> GetAllLayerProperties()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerPropertyGroup");
|
||||
|
||||
if (!PropertiesInitialized)
|
||||
return new List<ILayerProperty>();
|
||||
|
||||
List<ILayerProperty> result = new(LayerProperties);
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
result.AddRange(layerPropertyGroup.GetAllLayerProperties());
|
||||
|
||||
return result.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the default value to all layer properties
|
||||
/// </summary>
|
||||
public void ResetAllLayerProperties()
|
||||
{
|
||||
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
|
||||
layerProperty.ApplyDefaultValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the property group has initialized all its children
|
||||
/// </summary>
|
||||
public event EventHandler? PropertyGroupInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when one of the current value of one of the layer properties in this group changes by some form of input
|
||||
/// <para>Note: Will not trigger on properties in child groups</para>
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? LayerPropertyOnCurrentValueSet;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler? VisibilityChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Called before property group is activated to allow you to populate <see cref="LayerProperty{T}.DefaultValue" /> on
|
||||
/// the properties you want
|
||||
/// </summary>
|
||||
protected abstract void PopulateDefaults();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the property group is activated
|
||||
/// </summary>
|
||||
protected abstract void EnableProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the property group is deactivated (either the profile unloaded or the related brush/effect was removed)
|
||||
/// </summary>
|
||||
protected abstract void DisableProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the property group and all its layer properties have been initialized
|
||||
/// </summary>
|
||||
protected virtual void OnPropertyGroupInitialized()
|
||||
{
|
||||
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
DisableProperties();
|
||||
|
||||
foreach (ILayerProperty layerProperty in _layerProperties)
|
||||
layerProperty.Dispose();
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
|
||||
layerPropertyGroup.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal void Initialize(RenderProfileElement profileElement, LayerPropertyGroup? parent, PropertyGroupDescriptionAttribute groupDescription, PropertyGroupEntity? propertyGroupEntity)
|
||||
{
|
||||
if (groupDescription.Identifier == null)
|
||||
throw new ArtemisCoreException("Can't initialize a property group without an identifier");
|
||||
|
||||
// Doubt this will happen but let's make sure
|
||||
if (PropertiesInitialized)
|
||||
throw new ArtemisCoreException("Layer property group already initialized, wut");
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Parent = parent;
|
||||
GroupDescription = groupDescription;
|
||||
PropertyGroupEntity = propertyGroupEntity ?? new PropertyGroupEntity {Identifier = groupDescription.Identifier};
|
||||
Path = parent != null ? parent.Path + "." + groupDescription.Identifier : groupDescription.Identifier;
|
||||
|
||||
// Get all properties implementing ILayerProperty or LayerPropertyGroup
|
||||
foreach (PropertyInfo propertyInfo in GetType().GetProperties())
|
||||
{
|
||||
if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute)))
|
||||
continue;
|
||||
|
||||
if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
PropertyDescriptionAttribute? propertyDescription =
|
||||
(PropertyDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
|
||||
InitializeProperty(propertyInfo, propertyDescription ?? new PropertyDescriptionAttribute());
|
||||
}
|
||||
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
PropertyGroupDescriptionAttribute? propertyGroupDescription =
|
||||
(PropertyGroupDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
|
||||
InitializeChildGroup(propertyInfo, propertyGroupDescription ?? new PropertyGroupDescriptionAttribute());
|
||||
}
|
||||
}
|
||||
|
||||
// Request the property group to populate defaults
|
||||
PopulateDefaults();
|
||||
|
||||
// Load the layer properties after defaults have been applied
|
||||
foreach (ILayerProperty layerProperty in _layerProperties)
|
||||
layerProperty.Load();
|
||||
|
||||
EnableProperties();
|
||||
PropertiesInitialized = true;
|
||||
OnPropertyGroupInitialized();
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
{
|
||||
if (!PropertiesInitialized || PropertyGroupEntity == null)
|
||||
return;
|
||||
|
||||
foreach (ILayerProperty layerProperty in LayerProperties)
|
||||
layerProperty.Save();
|
||||
|
||||
PropertyGroupEntity.PropertyGroups.Clear();
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
{
|
||||
layerPropertyGroup.ApplyToEntity();
|
||||
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(Timeline timeline)
|
||||
{
|
||||
foreach (ILayerProperty layerProperty in LayerProperties)
|
||||
layerProperty.Update(timeline);
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
layerPropertyGroup.Update(timeline);
|
||||
}
|
||||
|
||||
internal void MoveLayerProperty(ILayerProperty layerProperty, int index)
|
||||
{
|
||||
if (!_layerProperties.Contains(layerProperty))
|
||||
return;
|
||||
|
||||
_layerProperties.Remove(layerProperty);
|
||||
_layerProperties.Insert(index, layerProperty);
|
||||
}
|
||||
|
||||
internal virtual void OnVisibilityChanged()
|
||||
{
|
||||
VisibilityChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
|
||||
{
|
||||
Parent?.OnLayerPropertyOnCurrentValueSet(e);
|
||||
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
|
||||
{
|
||||
// Ensure the description has an identifier and name, if not this is a good point to set it based on the property info
|
||||
if (string.IsNullOrWhiteSpace(propertyDescription.Identifier))
|
||||
propertyDescription.Identifier = propertyInfo.Name;
|
||||
if (string.IsNullOrWhiteSpace(propertyDescription.Name))
|
||||
propertyDescription.Name = propertyInfo.Name.Humanize();
|
||||
|
||||
if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
throw new ArtemisPluginException($"Property with PropertyDescription attribute must be of type ILayerProperty: {propertyDescription.Identifier}");
|
||||
if (Activator.CreateInstance(propertyInfo.PropertyType, true) is not ILayerProperty instance)
|
||||
throw new ArtemisPluginException($"Failed to create instance of layer property: {propertyDescription.Identifier}");
|
||||
|
||||
PropertyEntity entity = GetPropertyEntity(propertyDescription.Identifier, out bool fromStorage);
|
||||
instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription);
|
||||
propertyInfo.SetValue(this, instance);
|
||||
|
||||
_layerProperties.Add(instance);
|
||||
}
|
||||
|
||||
private void InitializeChildGroup(PropertyInfo propertyInfo, PropertyGroupDescriptionAttribute propertyGroupDescription)
|
||||
{
|
||||
// Ensure the description has an identifier and name name, if not this is a good point to set it based on the property info
|
||||
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Identifier))
|
||||
propertyGroupDescription.Identifier = propertyInfo.Name;
|
||||
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Name))
|
||||
propertyGroupDescription.Name = propertyInfo.Name.Humanize();
|
||||
|
||||
if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
throw new ArtemisPluginException($"Property with PropertyGroupDescription attribute must be of type LayerPropertyGroup: {propertyGroupDescription.Identifier}");
|
||||
if (!(Activator.CreateInstance(propertyInfo.PropertyType) is LayerPropertyGroup instance))
|
||||
throw new ArtemisPluginException($"Failed to create instance of layer property group: {propertyGroupDescription.Identifier}");
|
||||
|
||||
PropertyGroupEntity entity = GetPropertyGroupEntity(propertyGroupDescription.Identifier);
|
||||
instance.Initialize(ProfileElement, this, propertyGroupDescription, entity);
|
||||
|
||||
propertyInfo.SetValue(this, instance);
|
||||
_layerPropertyGroups.Add(instance);
|
||||
}
|
||||
|
||||
private PropertyEntity GetPropertyEntity(string identifier, out bool fromStorage)
|
||||
{
|
||||
if (PropertyGroupEntity == null)
|
||||
throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyEntity)} without {nameof(PropertyGroupEntity)} being setup");
|
||||
|
||||
PropertyEntity? entity = PropertyGroupEntity.Properties.FirstOrDefault(p => p.Identifier == identifier);
|
||||
fromStorage = entity != null;
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new PropertyEntity {Identifier = identifier};
|
||||
PropertyGroupEntity.Properties.Add(entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private PropertyGroupEntity GetPropertyGroupEntity(string identifier)
|
||||
{
|
||||
if (PropertyGroupEntity == null)
|
||||
throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyGroupEntity)} without {nameof(PropertyGroupEntity)} being setup");
|
||||
|
||||
return PropertyGroupEntity.PropertyGroups.FirstOrDefault(g => g.Identifier == identifier) ?? new PropertyGroupEntity() {Identifier = identifier};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
_isHidden = value;
|
||||
OnVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity this property group uses for persistent storage
|
||||
/// </summary>
|
||||
public PropertyGroupEntity? PropertyGroupEntity { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all layer properties in this group
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ILayerProperty> LayerProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of al child groups in this group
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Recursively gets all layer properties on this group and any subgroups
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ILayerProperty> GetAllLayerProperties()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerPropertyGroup");
|
||||
|
||||
if (!PropertiesInitialized)
|
||||
return new List<ILayerProperty>();
|
||||
|
||||
List<ILayerProperty> result = new(LayerProperties);
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
result.AddRange(layerPropertyGroup.GetAllLayerProperties());
|
||||
|
||||
return result.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the default value to all layer properties
|
||||
/// </summary>
|
||||
public void ResetAllLayerProperties()
|
||||
{
|
||||
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
|
||||
layerProperty.ApplyDefaultValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the property group has initialized all its children
|
||||
/// </summary>
|
||||
public event EventHandler? PropertyGroupInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when one of the current value of one of the layer properties in this group changes by some form of input
|
||||
/// <para>Note: Will not trigger on properties in child groups</para>
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? LayerPropertyOnCurrentValueSet;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler? VisibilityChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Called before property group is activated to allow you to populate <see cref="LayerProperty{T}.DefaultValue" /> on
|
||||
/// the properties you want
|
||||
/// </summary>
|
||||
protected abstract void PopulateDefaults();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the property group is activated
|
||||
/// </summary>
|
||||
protected abstract void EnableProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the property group is deactivated (either the profile unloaded or the related brush/effect was removed)
|
||||
/// </summary>
|
||||
protected abstract void DisableProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the property group and all its layer properties have been initialized
|
||||
/// </summary>
|
||||
protected virtual void OnPropertyGroupInitialized()
|
||||
{
|
||||
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
DisableProperties();
|
||||
|
||||
foreach (ILayerProperty layerProperty in _layerProperties)
|
||||
layerProperty.Dispose();
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
|
||||
layerPropertyGroup.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal void Initialize(RenderProfileElement profileElement, LayerPropertyGroup? parent, PropertyGroupDescriptionAttribute groupDescription, PropertyGroupEntity? propertyGroupEntity)
|
||||
{
|
||||
if (groupDescription.Identifier == null)
|
||||
throw new ArtemisCoreException("Can't initialize a property group without an identifier");
|
||||
|
||||
// Doubt this will happen but let's make sure
|
||||
if (PropertiesInitialized)
|
||||
throw new ArtemisCoreException("Layer property group already initialized, wut");
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Parent = parent;
|
||||
GroupDescription = groupDescription;
|
||||
PropertyGroupEntity = propertyGroupEntity ?? new PropertyGroupEntity {Identifier = groupDescription.Identifier};
|
||||
Path = parent != null ? parent.Path + "." + groupDescription.Identifier : groupDescription.Identifier;
|
||||
|
||||
// Get all properties implementing ILayerProperty or LayerPropertyGroup
|
||||
foreach (PropertyInfo propertyInfo in GetType().GetProperties())
|
||||
{
|
||||
if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute)))
|
||||
continue;
|
||||
|
||||
if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
PropertyDescriptionAttribute? propertyDescription =
|
||||
(PropertyDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
|
||||
InitializeProperty(propertyInfo, propertyDescription ?? new PropertyDescriptionAttribute());
|
||||
}
|
||||
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
PropertyGroupDescriptionAttribute? propertyGroupDescription =
|
||||
(PropertyGroupDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
|
||||
InitializeChildGroup(propertyInfo, propertyGroupDescription ?? new PropertyGroupDescriptionAttribute());
|
||||
}
|
||||
}
|
||||
|
||||
// Request the property group to populate defaults
|
||||
PopulateDefaults();
|
||||
|
||||
// Load the layer properties after defaults have been applied
|
||||
foreach (ILayerProperty layerProperty in _layerProperties)
|
||||
layerProperty.Load();
|
||||
|
||||
EnableProperties();
|
||||
PropertiesInitialized = true;
|
||||
OnPropertyGroupInitialized();
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
{
|
||||
if (!PropertiesInitialized || PropertyGroupEntity == null)
|
||||
return;
|
||||
|
||||
foreach (ILayerProperty layerProperty in LayerProperties)
|
||||
layerProperty.Save();
|
||||
|
||||
PropertyGroupEntity.PropertyGroups.Clear();
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
{
|
||||
layerPropertyGroup.ApplyToEntity();
|
||||
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(Timeline timeline)
|
||||
{
|
||||
foreach (ILayerProperty layerProperty in LayerProperties)
|
||||
layerProperty.Update(timeline);
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
layerPropertyGroup.Update(timeline);
|
||||
}
|
||||
|
||||
internal void MoveLayerProperty(ILayerProperty layerProperty, int index)
|
||||
{
|
||||
if (!_layerProperties.Contains(layerProperty))
|
||||
return;
|
||||
|
||||
_layerProperties.Remove(layerProperty);
|
||||
_layerProperties.Insert(index, layerProperty);
|
||||
}
|
||||
|
||||
internal virtual void OnVisibilityChanged()
|
||||
{
|
||||
VisibilityChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
|
||||
{
|
||||
Parent?.OnLayerPropertyOnCurrentValueSet(e);
|
||||
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
|
||||
{
|
||||
// Ensure the description has an identifier and name, if not this is a good point to set it based on the property info
|
||||
if (string.IsNullOrWhiteSpace(propertyDescription.Identifier))
|
||||
propertyDescription.Identifier = propertyInfo.Name;
|
||||
if (string.IsNullOrWhiteSpace(propertyDescription.Name))
|
||||
propertyDescription.Name = propertyInfo.Name.Humanize();
|
||||
|
||||
if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
throw new ArtemisPluginException($"Property with PropertyDescription attribute must be of type ILayerProperty: {propertyDescription.Identifier}");
|
||||
if (Activator.CreateInstance(propertyInfo.PropertyType, true) is not ILayerProperty instance)
|
||||
throw new ArtemisPluginException($"Failed to create instance of layer property: {propertyDescription.Identifier}");
|
||||
|
||||
PropertyEntity entity = GetPropertyEntity(propertyDescription.Identifier, out bool fromStorage);
|
||||
instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription);
|
||||
propertyInfo.SetValue(this, instance);
|
||||
|
||||
_layerProperties.Add(instance);
|
||||
}
|
||||
|
||||
private void InitializeChildGroup(PropertyInfo propertyInfo, PropertyGroupDescriptionAttribute propertyGroupDescription)
|
||||
{
|
||||
// Ensure the description has an identifier and name name, if not this is a good point to set it based on the property info
|
||||
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Identifier))
|
||||
propertyGroupDescription.Identifier = propertyInfo.Name;
|
||||
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Name))
|
||||
propertyGroupDescription.Name = propertyInfo.Name.Humanize();
|
||||
|
||||
if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
throw new ArtemisPluginException($"Property with PropertyGroupDescription attribute must be of type LayerPropertyGroup: {propertyGroupDescription.Identifier}");
|
||||
if (!(Activator.CreateInstance(propertyInfo.PropertyType) is LayerPropertyGroup instance))
|
||||
throw new ArtemisPluginException($"Failed to create instance of layer property group: {propertyGroupDescription.Identifier}");
|
||||
|
||||
PropertyGroupEntity entity = GetPropertyGroupEntity(propertyGroupDescription.Identifier);
|
||||
instance.Initialize(ProfileElement, this, propertyGroupDescription, entity);
|
||||
|
||||
propertyInfo.SetValue(this, instance);
|
||||
_layerPropertyGroups.Add(instance);
|
||||
}
|
||||
|
||||
private PropertyEntity GetPropertyEntity(string identifier, out bool fromStorage)
|
||||
{
|
||||
if (PropertyGroupEntity == null)
|
||||
throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyEntity)} without {nameof(PropertyGroupEntity)} being setup");
|
||||
|
||||
PropertyEntity? entity = PropertyGroupEntity.Properties.FirstOrDefault(p => p.Identifier == identifier);
|
||||
fromStorage = entity != null;
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new PropertyEntity {Identifier = identifier};
|
||||
PropertyGroupEntity.Properties.Add(entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private PropertyGroupEntity GetPropertyGroupEntity(string identifier)
|
||||
{
|
||||
if (PropertyGroupEntity == null)
|
||||
throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyGroupEntity)} without {nameof(PropertyGroupEntity)} being setup");
|
||||
|
||||
return PropertyGroupEntity.PropertyGroups.FirstOrDefault(g => g.Identifier == identifier) ?? new PropertyGroupEntity {Identifier = identifier};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an ellipse layer shape
|
||||
/// </summary>
|
||||
public class EllipseShape : LayerShape
|
||||
{
|
||||
internal EllipseShape(Layer layer) : base(layer)
|
||||
{
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void CalculateRenderProperties()
|
||||
{
|
||||
SKPath path = new();
|
||||
path.AddOval(SKRect.Create(Layer.Bounds.Width, Layer.Bounds.Height));
|
||||
Path = path;
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents an ellipse layer shape
|
||||
/// </summary>
|
||||
public class EllipseShape : LayerShape
|
||||
{
|
||||
internal EllipseShape(Layer layer) : base(layer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void CalculateRenderProperties()
|
||||
{
|
||||
SKPath path = new();
|
||||
path.AddOval(SKRect.Create(Layer.Bounds.Width, Layer.Bounds.Height));
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,29 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the shape of a layer
|
||||
/// </summary>
|
||||
public abstract class LayerShape
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the shape of a layer
|
||||
/// </summary>
|
||||
public abstract class LayerShape
|
||||
internal LayerShape(Layer layer)
|
||||
{
|
||||
internal LayerShape(Layer layer)
|
||||
{
|
||||
Layer = layer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layer this shape is attached to
|
||||
/// </summary>
|
||||
public Layer Layer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a the path outlining the shape
|
||||
/// </summary>
|
||||
public SKPath? Path { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the <see cref="Path" />
|
||||
/// </summary>
|
||||
public abstract void CalculateRenderProperties();
|
||||
Layer = layer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layer this shape is attached to
|
||||
/// </summary>
|
||||
public Layer Layer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a the path outlining the shape
|
||||
/// </summary>
|
||||
public SKPath? Path { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the <see cref="Path" />
|
||||
/// </summary>
|
||||
public abstract void CalculateRenderProperties();
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a rectangular layer shape
|
||||
/// </summary>
|
||||
public class RectangleShape : LayerShape
|
||||
{
|
||||
internal RectangleShape(Layer layer) : base(layer)
|
||||
{
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void CalculateRenderProperties()
|
||||
{
|
||||
SKPath path = new();
|
||||
path.AddRect(SKRect.Create(Layer.Bounds.Width, Layer.Bounds.Height));
|
||||
Path = path;
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a rectangular layer shape
|
||||
/// </summary>
|
||||
public class RectangleShape : LayerShape
|
||||
{
|
||||
internal RectangleShape(Layer layer) : base(layer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void CalculateRenderProperties()
|
||||
{
|
||||
SKPath path = new();
|
||||
path.AddRect(SKRect.Create(Layer.Bounds.Width, Layer.Bounds.Height));
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
@ -2,60 +2,59 @@
|
||||
|
||||
#pragma warning disable 8618
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the transform properties of a layer
|
||||
/// </summary>
|
||||
public class LayerTransformProperties : LayerPropertyGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the transform properties of a layer
|
||||
/// The point at which the shape is attached to its position
|
||||
/// </summary>
|
||||
public class LayerTransformProperties : LayerPropertyGroup
|
||||
[PropertyDescription(Description = "The point at which the shape is attached to its position", InputAffix = "%", InputStepSize = 0.001f)]
|
||||
public SKPointLayerProperty AnchorPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position of the shape
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The position of the shape", InputAffix = "%", InputStepSize = 0.001f)]
|
||||
public SKPointLayerProperty Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scale of the shape
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The scale of the shape", InputAffix = "%", MinInputValue = 0f)]
|
||||
public SKSizeLayerProperty Scale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the shape in degree
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The rotation of the shape in degrees", InputAffix = "°", InputStepSize = 0.5f)]
|
||||
public FloatLayerProperty Rotation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The opacity of the shape
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f, InputStepSize = 0.1f)]
|
||||
public FloatLayerProperty Opacity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PopulateDefaults()
|
||||
{
|
||||
/// <summary>
|
||||
/// The point at which the shape is attached to its position
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The point at which the shape is attached to its position", InputAffix = "%", InputStepSize = 0.001f)]
|
||||
public SKPointLayerProperty AnchorPoint { get; set; }
|
||||
Scale.DefaultValue = new SKSize(100, 100);
|
||||
AnchorPoint.DefaultValue = new SKPoint(0.5f, 0.5f);
|
||||
Position.DefaultValue = new SKPoint(0.5f, 0.5f);
|
||||
Opacity.DefaultValue = 100;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The position of the shape
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The position of the shape", InputAffix = "%", InputStepSize = 0.001f)]
|
||||
public SKPointLayerProperty Position { get; set; }
|
||||
/// <inheritdoc />
|
||||
protected override void EnableProperties()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The scale of the shape
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The scale of the shape", InputAffix = "%", MinInputValue = 0f)]
|
||||
public SKSizeLayerProperty Scale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the shape in degree
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The rotation of the shape in degrees", InputAffix = "°", InputStepSize = 0.5f)]
|
||||
public FloatLayerProperty Rotation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The opacity of the shape
|
||||
/// </summary>
|
||||
[PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f, InputStepSize = 0.1f)]
|
||||
public FloatLayerProperty Opacity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PopulateDefaults()
|
||||
{
|
||||
Scale.DefaultValue = new SKSize(100, 100);
|
||||
AnchorPoint.DefaultValue = new SKPoint(0.5f, 0.5f);
|
||||
Position.DefaultValue = new SKPoint(0.5f, 0.5f);
|
||||
Opacity.DefaultValue = 100;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void EnableProperties()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisableProperties()
|
||||
{
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void DisableProperties()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -6,301 +6,299 @@ using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile containing folders and layers
|
||||
/// </summary>
|
||||
public sealed class Profile : ProfileElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a profile containing folders and layers
|
||||
/// </summary>
|
||||
public sealed class Profile : ProfileElement
|
||||
private readonly object _lock = new();
|
||||
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
|
||||
private readonly ObservableCollection<ProfileScript> _scripts;
|
||||
private bool _isFreshImport;
|
||||
private ProfileElement? _lastSelectedProfileElement;
|
||||
|
||||
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private bool _isFreshImport;
|
||||
private ProfileElement? _lastSelectedProfileElement;
|
||||
private readonly ObservableCollection<ProfileScript> _scripts;
|
||||
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
|
||||
_scripts = new ObservableCollection<ProfileScript>();
|
||||
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
|
||||
|
||||
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
|
||||
{
|
||||
_scripts = new ObservableCollection<ProfileScript>();
|
||||
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
|
||||
Configuration = configuration;
|
||||
Profile = this;
|
||||
ProfileEntity = profileEntity;
|
||||
EntityId = profileEntity.Id;
|
||||
|
||||
Configuration = configuration;
|
||||
Profile = this;
|
||||
ProfileEntity = profileEntity;
|
||||
EntityId = profileEntity.Id;
|
||||
Exceptions = new List<Exception>();
|
||||
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
|
||||
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
|
||||
|
||||
Exceptions = new List<Exception>();
|
||||
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
|
||||
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
|
||||
Load();
|
||||
}
|
||||
|
||||
Load();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the profile configuration of this profile
|
||||
/// </summary>
|
||||
public ProfileConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile configuration of this profile
|
||||
/// </summary>
|
||||
public ProfileConfiguration Configuration { get; }
|
||||
/// <summary>
|
||||
/// Gets a collection of all active scripts assigned to this profile
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<ProfileScript> Scripts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all active scripts assigned to this profile
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<ProfileScript> Scripts { get; }
|
||||
/// <summary>
|
||||
/// Gets a collection of all script configurations assigned to this profile
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<ScriptConfiguration> ScriptConfigurations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all script configurations assigned to this profile
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<ScriptConfiguration> ScriptConfigurations { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
|
||||
/// since import
|
||||
/// <para>
|
||||
/// Note: As long as this is <see langword="true" />, profile adaption will be performed on load and any surface
|
||||
/// changes
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool IsFreshImport
|
||||
{
|
||||
get => _isFreshImport;
|
||||
set => SetAndNotify(ref _isFreshImport, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
|
||||
/// since import
|
||||
/// <para>
|
||||
/// Note: As long as this is <see langword="true" />, profile adaption will be performed on load and any surface
|
||||
/// changes
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool IsFreshImport
|
||||
{
|
||||
get => _isFreshImport;
|
||||
set => SetAndNotify(ref _isFreshImport, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the last selected profile element of this profile
|
||||
/// </summary>
|
||||
public ProfileElement? LastSelectedProfileElement
|
||||
{
|
||||
get => _lastSelectedProfileElement;
|
||||
set => SetAndNotify(ref _lastSelectedProfileElement, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last selected profile element of this profile
|
||||
/// </summary>
|
||||
public ProfileElement? LastSelectedProfileElement
|
||||
{
|
||||
get => _lastSelectedProfileElement;
|
||||
set => SetAndNotify(ref _lastSelectedProfileElement, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the profile entity this profile uses for persistent storage
|
||||
/// </summary>
|
||||
public ProfileEntity ProfileEntity { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile entity this profile uses for persistent storage
|
||||
/// </summary>
|
||||
public ProfileEntity ProfileEntity { get; internal set; }
|
||||
internal List<Exception> Exceptions { get; }
|
||||
|
||||
internal List<Exception> Exceptions { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdating(deltaTime);
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Update(deltaTime);
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdated(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Render(canvas, basePosition, editorFocus);
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
|
||||
|
||||
if (!Exceptions.Any())
|
||||
return;
|
||||
|
||||
List<Exception> exceptions = new(Exceptions);
|
||||
Exceptions.Clear();
|
||||
throw new AggregateException($"One or more exceptions while rendering profile {Name}", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Reset()
|
||||
{
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the root folder of this profile
|
||||
/// </summary>
|
||||
/// <returns>The root folder of the profile</returns>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public Folder GetRootFolder()
|
||||
/// <inheritdoc />
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
return (Folder) Children.Single();
|
||||
}
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdating(deltaTime);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Profile] {nameof(Name)}: {Name}";
|
||||
}
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Update(deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Populates all the LEDs on the elements in this profile
|
||||
/// </summary>
|
||||
/// <param name="devices">The devices to use while populating LEDs</param>
|
||||
public void PopulateLeds(IEnumerable<ArtemisDevice> devices)
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdated(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (Layer layer in GetAllLayers())
|
||||
layer.PopulateLeds(devices);
|
||||
}
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Render(canvas, basePosition, editorFocus);
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
|
||||
|
||||
if (!Exceptions.Any())
|
||||
return;
|
||||
|
||||
while (Scripts.Count > 0)
|
||||
RemoveScript(Scripts[0]);
|
||||
List<Exception> exceptions = new(Exceptions);
|
||||
Exceptions.Clear();
|
||||
throw new AggregateException($"One or more exceptions while rendering profile {Name}", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Reset()
|
||||
{
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the root folder of this profile
|
||||
/// </summary>
|
||||
/// <returns>The root folder of the profile</returns>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public Folder GetRootFolder()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
return (Folder) Children.Single();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Profile] {nameof(Name)}: {Name}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates all the LEDs on the elements in this profile
|
||||
/// </summary>
|
||||
/// <param name="devices">The devices to use while populating LEDs</param>
|
||||
public void PopulateLeds(IEnumerable<ArtemisDevice> devices)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (Layer layer in GetAllLayers())
|
||||
layer.PopulateLeds(devices);
|
||||
}
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<IBreakableModel> GetBrokenHierarchy()
|
||||
{
|
||||
return GetAllRenderElements().SelectMany(folders => folders.GetBrokenHierarchy());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
while (Scripts.Count > 0)
|
||||
RemoveScript(Scripts[0]);
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
ChildrenList.Clear();
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
internal override void Load()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
Name = Configuration.Name;
|
||||
IsFreshImport = ProfileEntity.IsFreshImport;
|
||||
|
||||
lock (ChildrenList)
|
||||
{
|
||||
// Remove the old profile tree
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
ChildrenList.Clear();
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
internal override void Load()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
Name = Configuration.Name;
|
||||
IsFreshImport = ProfileEntity.IsFreshImport;
|
||||
|
||||
lock (ChildrenList)
|
||||
{
|
||||
// Remove the old profile tree
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
ChildrenList.Clear();
|
||||
|
||||
// Populate the profile starting at the root, the rest is populated recursively
|
||||
FolderEntity? rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId);
|
||||
if (rootFolder == null)
|
||||
AddChild(new Folder(this, "Root folder"));
|
||||
else
|
||||
AddChild(new Folder(this, this, rootFolder));
|
||||
}
|
||||
|
||||
List<RenderProfileElement> renderElements = GetAllRenderElements();
|
||||
|
||||
if (ProfileEntity.LastSelectedProfileElement != Guid.Empty)
|
||||
LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement);
|
||||
// Populate the profile starting at the root, the rest is populated recursively
|
||||
FolderEntity? rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId);
|
||||
if (rootFolder == null)
|
||||
AddChild(new Folder(this, "Root folder"));
|
||||
else
|
||||
LastSelectedProfileElement = null;
|
||||
|
||||
while (_scriptConfigurations.Any())
|
||||
RemoveScriptConfiguration(_scriptConfigurations[0]);
|
||||
foreach (ScriptConfiguration scriptConfiguration in ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)))
|
||||
AddScriptConfiguration(scriptConfiguration);
|
||||
|
||||
// Load node scripts last since they may rely on the profile structure being in place
|
||||
foreach (RenderProfileElement renderProfileElement in renderElements)
|
||||
renderProfileElement.LoadNodeScript();
|
||||
AddChild(new Folder(this, this, rootFolder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a script configuration from the profile, if the configuration has an active script it is also removed.
|
||||
/// </summary>
|
||||
internal void RemoveScriptConfiguration(ScriptConfiguration scriptConfiguration)
|
||||
List<RenderProfileElement> renderElements = GetAllRenderElements();
|
||||
|
||||
if (ProfileEntity.LastSelectedProfileElement != Guid.Empty)
|
||||
LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement);
|
||||
else
|
||||
LastSelectedProfileElement = null;
|
||||
|
||||
while (_scriptConfigurations.Any())
|
||||
RemoveScriptConfiguration(_scriptConfigurations[0]);
|
||||
foreach (ScriptConfiguration scriptConfiguration in ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)))
|
||||
AddScriptConfiguration(scriptConfiguration);
|
||||
|
||||
// Load node scripts last since they may rely on the profile structure being in place
|
||||
foreach (RenderProfileElement renderProfileElement in renderElements)
|
||||
renderProfileElement.LoadNodeScript();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a script configuration from the profile, if the configuration has an active script it is also removed.
|
||||
/// </summary>
|
||||
internal void RemoveScriptConfiguration(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(scriptConfiguration))
|
||||
return;
|
||||
|
||||
Script? script = scriptConfiguration.Script;
|
||||
if (script != null)
|
||||
RemoveScript((ProfileScript) script);
|
||||
|
||||
_scriptConfigurations.Remove(scriptConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a script configuration to the profile but does not instantiate it's script.
|
||||
/// </summary>
|
||||
internal void AddScriptConfiguration(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(scriptConfiguration))
|
||||
_scriptConfigurations.Add(scriptConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a script that has a script configuration belonging to this profile.
|
||||
/// </summary>
|
||||
internal void AddScript(ProfileScript script)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(script.ScriptConfiguration))
|
||||
throw new ArtemisCoreException("Cannot add a script to a profile whose script configuration doesn't belong to the same profile.");
|
||||
|
||||
if (!_scripts.Contains(script))
|
||||
_scripts.Add(script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a script from the profile and disposes it.
|
||||
/// </summary>
|
||||
internal void RemoveScript(ProfileScript script)
|
||||
{
|
||||
_scripts.Remove(script);
|
||||
script.Dispose();
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
ProfileEntity.Id = EntityId;
|
||||
ProfileEntity.Name = Configuration.Name;
|
||||
ProfileEntity.IsFreshImport = IsFreshImport;
|
||||
ProfileEntity.LastSelectedProfileElement = LastSelectedProfileElement?.EntityId ?? Guid.Empty;
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Save();
|
||||
|
||||
ProfileEntity.Folders.Clear();
|
||||
ProfileEntity.Folders.AddRange(GetAllFolders().Select(f => f.FolderEntity));
|
||||
|
||||
ProfileEntity.Layers.Clear();
|
||||
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
|
||||
|
||||
ProfileEntity.ScriptConfigurations.Clear();
|
||||
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(scriptConfiguration))
|
||||
return;
|
||||
|
||||
Script? script = scriptConfiguration.Script;
|
||||
if (script != null)
|
||||
RemoveScript((ProfileScript) script);
|
||||
|
||||
_scriptConfigurations.Remove(scriptConfiguration);
|
||||
scriptConfiguration.Save();
|
||||
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a script configuration to the profile but does not instantiate it's script.
|
||||
/// </summary>
|
||||
internal void AddScriptConfiguration(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(scriptConfiguration))
|
||||
_scriptConfigurations.Add(scriptConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a script that has a script configuration belonging to this profile.
|
||||
/// </summary>
|
||||
internal void AddScript(ProfileScript script)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(script.ScriptConfiguration))
|
||||
throw new ArtemisCoreException("Cannot add a script to a profile whose script configuration doesn't belong to the same profile.");
|
||||
|
||||
if (!_scripts.Contains(script))
|
||||
_scripts.Add(script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a script from the profile and disposes it.
|
||||
/// </summary>
|
||||
internal void RemoveScript(ProfileScript script)
|
||||
{
|
||||
_scripts.Remove(script);
|
||||
script.Dispose();
|
||||
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
ProfileEntity.Id = EntityId;
|
||||
ProfileEntity.Name = Configuration.Name;
|
||||
ProfileEntity.IsFreshImport = IsFreshImport;
|
||||
ProfileEntity.LastSelectedProfileElement = LastSelectedProfileElement?.EntityId ?? Guid.Empty;
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Save();
|
||||
|
||||
ProfileEntity.Folders.Clear();
|
||||
ProfileEntity.Folders.AddRange(GetAllFolders().Select(f => f.FolderEntity));
|
||||
|
||||
ProfileEntity.Layers.Clear();
|
||||
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
|
||||
|
||||
ProfileEntity.ScriptConfigurations.Clear();
|
||||
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
|
||||
{
|
||||
scriptConfiguration.Save();
|
||||
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<IBreakableModel> GetBrokenHierarchy()
|
||||
{
|
||||
return GetAllRenderElements().SelectMany(folders => folders.GetBrokenHierarchy());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -3,212 +3,210 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a category containing <see cref="ProfileConfigurations" />
|
||||
/// </summary>
|
||||
public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
{
|
||||
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
||||
private bool _isCollapsed;
|
||||
private bool _isSuspended;
|
||||
private string _name;
|
||||
private int _order;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ProfileCategory" /> class
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the category</param>
|
||||
internal ProfileCategory(string name)
|
||||
{
|
||||
_name = name;
|
||||
Entity = new ProfileCategoryEntity();
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
}
|
||||
|
||||
internal ProfileCategory(ProfileCategoryEntity entity)
|
||||
{
|
||||
_name = null!;
|
||||
Entity = entity;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the profile category
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetAndNotify(ref _name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The order in which this category appears in the update loop and sidebar
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
get => _order;
|
||||
set => SetAndNotify(ref _order, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the category is collapsed or not
|
||||
/// <para>Note: Has no implications other than inside the UI</para>
|
||||
/// </summary>
|
||||
public bool IsCollapsed
|
||||
{
|
||||
get => _isCollapsed;
|
||||
set => SetAndNotify(ref _isCollapsed, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this category is suspended, disabling all its profiles
|
||||
/// </summary>
|
||||
public bool IsSuspended
|
||||
{
|
||||
get => _isSuspended;
|
||||
set => SetAndNotify(ref _isSuspended, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of the profiles inside this category
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this category
|
||||
/// </summary>
|
||||
public Guid EntityId => Entity.Id;
|
||||
|
||||
internal ProfileCategoryEntity Entity { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a profile configuration to this category
|
||||
/// </summary>
|
||||
public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
|
||||
{
|
||||
// Removing the original will shift every item in the list forwards, keep that in mind with the target index
|
||||
if (configuration.Category == this && targetIndex != null && targetIndex.Value > _profileConfigurations.IndexOf(configuration))
|
||||
targetIndex -= 1;
|
||||
|
||||
configuration.Category.RemoveProfileConfiguration(configuration);
|
||||
|
||||
if (targetIndex != null)
|
||||
{
|
||||
targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count);
|
||||
_profileConfigurations.Insert(targetIndex.Value, configuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
_profileConfigurations.Add(configuration);
|
||||
}
|
||||
|
||||
configuration.Category = this;
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
OnProfileConfigurationAdded(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[ProfileCategory] {Order} {nameof(Name)}: {Name}, {nameof(IsSuspended)}: {IsSuspended}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a profile configuration is added to this <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileConfigurationAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a profile configuration is removed from this <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileConfigurationRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ProfileConfigurationAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnProfileConfigurationAdded(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileConfigurationAdded?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ProfileConfigurationRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnProfileConfigurationRemoved(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileConfigurationRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
internal void RemoveProfileConfiguration(ProfileConfiguration configuration)
|
||||
{
|
||||
if (!_profileConfigurations.Remove(configuration))
|
||||
return;
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
OnProfileConfigurationRemoved(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
Name = Entity.Name;
|
||||
IsCollapsed = Entity.IsCollapsed;
|
||||
IsSuspended = Entity.IsSuspended;
|
||||
Order = Entity.Order;
|
||||
|
||||
_profileConfigurations.Clear();
|
||||
foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations)
|
||||
_profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Entity.Name = Name;
|
||||
Entity.IsCollapsed = IsCollapsed;
|
||||
Entity.IsSuspended = IsSuspended;
|
||||
Entity.Order = Order;
|
||||
|
||||
Entity.ProfileConfigurations.Clear();
|
||||
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||
{
|
||||
profileConfiguration.Save();
|
||||
Entity.ProfileConfigurations.Add(profileConfiguration.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a name of one of the default categories
|
||||
/// </summary>
|
||||
public enum DefaultCategoryName
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a category containing <see cref="ProfileConfigurations" />
|
||||
/// The category used by profiles tied to games
|
||||
/// </summary>
|
||||
public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
{
|
||||
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
||||
private bool _isCollapsed;
|
||||
private bool _isSuspended;
|
||||
private string _name;
|
||||
private int _order;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ProfileCategory" /> class
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the category</param>
|
||||
internal ProfileCategory(string name)
|
||||
{
|
||||
_name = name;
|
||||
Entity = new ProfileCategoryEntity();
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
}
|
||||
|
||||
internal ProfileCategory(ProfileCategoryEntity entity)
|
||||
{
|
||||
_name = null!;
|
||||
Entity = entity;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the profile category
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetAndNotify(ref _name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The order in which this category appears in the update loop and sidebar
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
get => _order;
|
||||
set => SetAndNotify(ref _order, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the category is collapsed or not
|
||||
/// <para>Note: Has no implications other than inside the UI</para>
|
||||
/// </summary>
|
||||
public bool IsCollapsed
|
||||
{
|
||||
get => _isCollapsed;
|
||||
set => SetAndNotify(ref _isCollapsed, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this category is suspended, disabling all its profiles
|
||||
/// </summary>
|
||||
public bool IsSuspended
|
||||
{
|
||||
get => _isSuspended;
|
||||
set => SetAndNotify(ref _isSuspended, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of the profiles inside this category
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this category
|
||||
/// </summary>
|
||||
public Guid EntityId => Entity.Id;
|
||||
|
||||
internal ProfileCategoryEntity Entity { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a profile configuration to this category
|
||||
/// </summary>
|
||||
public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
|
||||
{
|
||||
// Removing the original will shift every item in the list forwards, keep that in mind with the target index
|
||||
if (configuration.Category == this && targetIndex != null && targetIndex.Value > _profileConfigurations.IndexOf(configuration))
|
||||
targetIndex -= 1;
|
||||
|
||||
configuration.Category.RemoveProfileConfiguration(configuration);
|
||||
|
||||
if (targetIndex != null)
|
||||
{
|
||||
targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count);
|
||||
_profileConfigurations.Insert(targetIndex.Value, configuration);
|
||||
}
|
||||
else
|
||||
_profileConfigurations.Add(configuration);
|
||||
configuration.Category = this;
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
OnProfileConfigurationAdded(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[ProfileCategory] {Order} {nameof(Name)}: {Name}, {nameof(IsSuspended)}: {IsSuspended}";
|
||||
}
|
||||
|
||||
internal void RemoveProfileConfiguration(ProfileConfiguration configuration)
|
||||
{
|
||||
if (!_profileConfigurations.Remove(configuration))
|
||||
return;
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
OnProfileConfigurationRemoved(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
Name = Entity.Name;
|
||||
IsCollapsed = Entity.IsCollapsed;
|
||||
IsSuspended = Entity.IsSuspended;
|
||||
Order = Entity.Order;
|
||||
|
||||
_profileConfigurations.Clear();
|
||||
foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations)
|
||||
_profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Entity.Name = Name;
|
||||
Entity.IsCollapsed = IsCollapsed;
|
||||
Entity.IsSuspended = IsSuspended;
|
||||
Entity.Order = Order;
|
||||
|
||||
Entity.ProfileConfigurations.Clear();
|
||||
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||
{
|
||||
profileConfiguration.Save();
|
||||
Entity.ProfileConfigurations.Add(profileConfiguration.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a profile configuration is added to this <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileConfigurationAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a profile configuration is removed from this <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileConfigurationRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ProfileConfigurationAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnProfileConfigurationAdded(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileConfigurationAdded?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ProfileConfigurationRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnProfileConfigurationRemoved(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileConfigurationRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Games,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a name of one of the default categories
|
||||
/// The category used by profiles tied to applications
|
||||
/// </summary>
|
||||
public enum DefaultCategoryName
|
||||
{
|
||||
/// <summary>
|
||||
/// The category used by profiles tied to games
|
||||
/// </summary>
|
||||
Games,
|
||||
Applications,
|
||||
|
||||
/// <summary>
|
||||
/// The category used by profiles tied to applications
|
||||
/// </summary>
|
||||
Applications,
|
||||
|
||||
/// <summary>
|
||||
/// The category used by general profiles
|
||||
/// </summary>
|
||||
General
|
||||
}
|
||||
/// <summary>
|
||||
/// The category used by general profiles
|
||||
/// </summary>
|
||||
General
|
||||
}
|
||||
@ -2,381 +2,379 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an element of a <see cref="Profile" />
|
||||
/// </summary>
|
||||
public abstract class ProfileElement : BreakableModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an element of a <see cref="Profile" />
|
||||
/// </summary>
|
||||
public abstract class ProfileElement : BreakableModel, IDisposable
|
||||
internal readonly List<ProfileElement> ChildrenList;
|
||||
private Guid _entityId;
|
||||
private string? _name;
|
||||
private int _order;
|
||||
private ProfileElement? _parent;
|
||||
private Profile _profile;
|
||||
private bool _suspended;
|
||||
|
||||
internal ProfileElement(Profile profile)
|
||||
{
|
||||
internal readonly List<ProfileElement> ChildrenList;
|
||||
private Guid _entityId;
|
||||
private string? _name;
|
||||
private int _order;
|
||||
private ProfileElement? _parent;
|
||||
private Profile _profile;
|
||||
private bool _suspended;
|
||||
|
||||
internal ProfileElement(Profile profile)
|
||||
{
|
||||
_profile = profile;
|
||||
ChildrenList = new List<ProfileElement>();
|
||||
Children = new ReadOnlyCollection<ProfileElement>(ChildrenList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this profile element
|
||||
/// </summary>
|
||||
public Guid EntityId
|
||||
{
|
||||
get => _entityId;
|
||||
internal set => SetAndNotify(ref _entityId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile this element belongs to
|
||||
/// </summary>
|
||||
public Profile Profile
|
||||
{
|
||||
get => _profile;
|
||||
internal set => SetAndNotify(ref _profile, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this element
|
||||
/// </summary>
|
||||
public ProfileElement? Parent
|
||||
{
|
||||
get => _parent;
|
||||
internal set => SetAndNotify(ref _parent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element's children
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileElement> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The order in which this element appears in the update loop and editor
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
get => _order;
|
||||
internal set => SetAndNotify(ref _order, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name which appears in the editor
|
||||
/// </summary>
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetAndNotify(ref _name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the suspended state, if suspended the element is skipped in render and update
|
||||
/// </summary>
|
||||
public bool Suspended
|
||||
{
|
||||
get => _suspended;
|
||||
set => SetAndNotify(ref _suspended, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the profile element is disposed
|
||||
/// </summary>
|
||||
public bool Disposed { get; protected set; }
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string BrokenDisplayName => Name ?? GetType().Name;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Updates the element
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public abstract void Update(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the element
|
||||
/// </summary>
|
||||
/// <param name="canvas">The canvas to render upon.</param>
|
||||
/// <param name="basePosition">The base position to use to translate relative positions to absolute positions.</param>
|
||||
/// <param name="editorFocus">An optional element to focus on while rendering (other elements will not render).</param>
|
||||
public abstract void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the internal state of the element
|
||||
/// </summary>
|
||||
public abstract void Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was added to the <see cref="Children" /> list
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? ChildAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was removed from the <see cref="Children" /> list
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? ChildRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was added to the <see cref="Children" /> list of this element or any of it's descendents.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? DescendentAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was removed from the <see cref="Children" /> list of this element or any of it's descendents.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? DescendentRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ChildAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildAdded(ProfileElement child)
|
||||
{
|
||||
ChildAdded?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ChildRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildRemoved(ProfileElement child)
|
||||
{
|
||||
ChildRemoved?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DescendentAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDescendentAdded(ProfileElement child)
|
||||
{
|
||||
DescendentAdded?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
Parent?.OnDescendentAdded(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DescendentRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDescendentRemoved(ProfileElement child)
|
||||
{
|
||||
DescendentRemoved?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
Parent?.OnDescendentRemoved(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the profile element
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Hierarchy
|
||||
|
||||
/// <summary>
|
||||
/// Adds a profile element to the <see cref="Children" /> collection, optionally at the given position (0-based)
|
||||
/// </summary>
|
||||
/// <param name="child">The profile element to add</param>
|
||||
/// <param name="order">The order where to place the child (0-based), defaults to the end of the collection</param>
|
||||
public virtual void AddChild(ProfileElement child, int? order = null)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
ProfileElement? current = this;
|
||||
while (current != null)
|
||||
{
|
||||
if (ReferenceEquals(child, this))
|
||||
throw new ArtemisCoreException("Cannot make an element a child of itself");
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
lock (ChildrenList)
|
||||
{
|
||||
if (ChildrenList.Contains(child))
|
||||
return;
|
||||
|
||||
// Add to the end of the list
|
||||
if (order == null)
|
||||
{
|
||||
ChildrenList.Add(child);
|
||||
}
|
||||
// Insert at the given index
|
||||
else
|
||||
{
|
||||
if (order < 0)
|
||||
order = 0;
|
||||
if (order > ChildrenList.Count)
|
||||
order = ChildrenList.Count;
|
||||
ChildrenList.Insert(order.Value, child);
|
||||
}
|
||||
|
||||
child.Parent = this;
|
||||
StreamlineOrder();
|
||||
}
|
||||
|
||||
OnChildAdded(child);
|
||||
OnDescendentAdded(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a profile element from the <see cref="Children" /> collection
|
||||
/// </summary>
|
||||
/// <param name="child">The profile element to remove</param>
|
||||
public virtual void RemoveChild(ProfileElement child)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
lock (ChildrenList)
|
||||
{
|
||||
ChildrenList.Remove(child);
|
||||
StreamlineOrder();
|
||||
|
||||
child.Parent = null;
|
||||
}
|
||||
|
||||
OnChildRemoved(child);
|
||||
OnDescendentRemoved(child);
|
||||
}
|
||||
|
||||
private void StreamlineOrder()
|
||||
{
|
||||
for (int index = 0; index < ChildrenList.Count; index++)
|
||||
ChildrenList[index].Order = index + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of all child render elements
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<RenderProfileElement> GetAllRenderElements()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
List<RenderProfileElement> elements = new();
|
||||
foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast<RenderProfileElement>())
|
||||
{
|
||||
// Add all folders in this element
|
||||
elements.Add(childElement);
|
||||
// Add all folders in folders inside this element
|
||||
elements.AddRange(childElement.GetAllRenderElements());
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of all child folders
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Folder> GetAllFolders()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
List<Folder> folders = new();
|
||||
foreach (Folder childFolder in Children.Where(c => c is Folder).Cast<Folder>())
|
||||
{
|
||||
// Add all folders in this element
|
||||
folders.Add(childFolder);
|
||||
// Add all folders in folders inside this element
|
||||
folders.AddRange(childFolder.GetAllFolders());
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of all child layers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Layer> GetAllLayers()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
List<Layer> layers = new();
|
||||
|
||||
// Add all layers in this element
|
||||
layers.AddRange(Children.Where(c => c is Layer).Cast<Layer>());
|
||||
|
||||
// Add all layers in folders inside this element
|
||||
foreach (Folder childFolder in Children.Where(c => c is Folder).Cast<Folder>())
|
||||
layers.AddRange(childFolder.GetAllLayers());
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name for a new layer according to any other layers with a default name similar to creating new folders in
|
||||
/// Explorer
|
||||
/// </summary>
|
||||
/// <returns>The resulting name i.e. <c>New layer</c> or <c>New layer (2)</c></returns>
|
||||
public string GetNewLayerName(string baseName = "New layer")
|
||||
{
|
||||
if (!Children.Any(c => c is Layer && c.Name == baseName))
|
||||
return baseName;
|
||||
|
||||
int current = 2;
|
||||
while (true)
|
||||
{
|
||||
if (Children.Where(c => c is Layer).All(c => c.Name != $"{baseName} ({current})"))
|
||||
return $"{baseName} ({current})";
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name for a new folder according to any other folders with a default name similar to creating new folders
|
||||
/// in Explorer
|
||||
/// </summary>
|
||||
/// <returns>The resulting name i.e. <c>New folder</c> or <c>New folder (2)</c></returns>
|
||||
public string GetNewFolderName(string baseName = "New folder")
|
||||
{
|
||||
if (!Children.Any(c => c is Folder && c.Name == baseName))
|
||||
return baseName;
|
||||
|
||||
int current = 2;
|
||||
while (true)
|
||||
{
|
||||
if (Children.Where(c => c is Folder).All(c => c.Name != $"{baseName} ({current})"))
|
||||
return $"{baseName} ({current})";
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
internal abstract void Load();
|
||||
internal abstract void Save();
|
||||
|
||||
#endregion
|
||||
_profile = profile;
|
||||
ChildrenList = new List<ProfileElement>();
|
||||
Children = new ReadOnlyCollection<ProfileElement>(ChildrenList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this profile element
|
||||
/// </summary>
|
||||
public Guid EntityId
|
||||
{
|
||||
get => _entityId;
|
||||
internal set => SetAndNotify(ref _entityId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile this element belongs to
|
||||
/// </summary>
|
||||
public Profile Profile
|
||||
{
|
||||
get => _profile;
|
||||
internal set => SetAndNotify(ref _profile, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this element
|
||||
/// </summary>
|
||||
public ProfileElement? Parent
|
||||
{
|
||||
get => _parent;
|
||||
internal set => SetAndNotify(ref _parent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element's children
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileElement> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The order in which this element appears in the update loop and editor
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
get => _order;
|
||||
internal set => SetAndNotify(ref _order, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name which appears in the editor
|
||||
/// </summary>
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetAndNotify(ref _name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the suspended state, if suspended the element is skipped in render and update
|
||||
/// </summary>
|
||||
public bool Suspended
|
||||
{
|
||||
get => _suspended;
|
||||
set => SetAndNotify(ref _suspended, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the profile element is disposed
|
||||
/// </summary>
|
||||
public bool Disposed { get; protected set; }
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string BrokenDisplayName => Name ?? GetType().Name;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Updates the element
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public abstract void Update(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the element
|
||||
/// </summary>
|
||||
/// <param name="canvas">The canvas to render upon.</param>
|
||||
/// <param name="basePosition">The base position to use to translate relative positions to absolute positions.</param>
|
||||
/// <param name="editorFocus">An optional element to focus on while rendering (other elements will not render).</param>
|
||||
public abstract void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the internal state of the element
|
||||
/// </summary>
|
||||
public abstract void Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was added to the <see cref="Children" /> list
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? ChildAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was removed from the <see cref="Children" /> list
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? ChildRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was added to the <see cref="Children" /> list of this element or any of it's descendents.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? DescendentAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was removed from the <see cref="Children" /> list of this element or any of it's descendents.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileElementEventArgs>? DescendentRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ChildAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildAdded(ProfileElement child)
|
||||
{
|
||||
ChildAdded?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ChildRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildRemoved(ProfileElement child)
|
||||
{
|
||||
ChildRemoved?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DescendentAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDescendentAdded(ProfileElement child)
|
||||
{
|
||||
DescendentAdded?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
Parent?.OnDescendentAdded(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DescendentRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDescendentRemoved(ProfileElement child)
|
||||
{
|
||||
DescendentRemoved?.Invoke(this, new ProfileElementEventArgs(child));
|
||||
Parent?.OnDescendentRemoved(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the profile element
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Hierarchy
|
||||
|
||||
/// <summary>
|
||||
/// Adds a profile element to the <see cref="Children" /> collection, optionally at the given position (0-based)
|
||||
/// </summary>
|
||||
/// <param name="child">The profile element to add</param>
|
||||
/// <param name="order">The order where to place the child (0-based), defaults to the end of the collection</param>
|
||||
public virtual void AddChild(ProfileElement child, int? order = null)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
ProfileElement? current = this;
|
||||
while (current != null)
|
||||
{
|
||||
if (ReferenceEquals(child, this))
|
||||
throw new ArtemisCoreException("Cannot make an element a child of itself");
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
lock (ChildrenList)
|
||||
{
|
||||
if (ChildrenList.Contains(child))
|
||||
return;
|
||||
|
||||
// Add to the end of the list
|
||||
if (order == null)
|
||||
{
|
||||
ChildrenList.Add(child);
|
||||
}
|
||||
// Insert at the given index
|
||||
else
|
||||
{
|
||||
if (order < 0)
|
||||
order = 0;
|
||||
if (order > ChildrenList.Count)
|
||||
order = ChildrenList.Count;
|
||||
ChildrenList.Insert(order.Value, child);
|
||||
}
|
||||
|
||||
child.Parent = this;
|
||||
StreamlineOrder();
|
||||
}
|
||||
|
||||
OnChildAdded(child);
|
||||
OnDescendentAdded(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a profile element from the <see cref="Children" /> collection
|
||||
/// </summary>
|
||||
/// <param name="child">The profile element to remove</param>
|
||||
public virtual void RemoveChild(ProfileElement child)
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
lock (ChildrenList)
|
||||
{
|
||||
ChildrenList.Remove(child);
|
||||
StreamlineOrder();
|
||||
|
||||
child.Parent = null;
|
||||
}
|
||||
|
||||
OnChildRemoved(child);
|
||||
OnDescendentRemoved(child);
|
||||
}
|
||||
|
||||
private void StreamlineOrder()
|
||||
{
|
||||
for (int index = 0; index < ChildrenList.Count; index++)
|
||||
ChildrenList[index].Order = index + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of all child render elements
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<RenderProfileElement> GetAllRenderElements()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
List<RenderProfileElement> elements = new();
|
||||
foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast<RenderProfileElement>())
|
||||
{
|
||||
// Add all folders in this element
|
||||
elements.Add(childElement);
|
||||
// Add all folders in folders inside this element
|
||||
elements.AddRange(childElement.GetAllRenderElements());
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of all child folders
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Folder> GetAllFolders()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
List<Folder> folders = new();
|
||||
foreach (Folder childFolder in Children.Where(c => c is Folder).Cast<Folder>())
|
||||
{
|
||||
// Add all folders in this element
|
||||
folders.Add(childFolder);
|
||||
// Add all folders in folders inside this element
|
||||
folders.AddRange(childFolder.GetAllFolders());
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of all child layers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Layer> GetAllLayers()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
|
||||
List<Layer> layers = new();
|
||||
|
||||
// Add all layers in this element
|
||||
layers.AddRange(Children.Where(c => c is Layer).Cast<Layer>());
|
||||
|
||||
// Add all layers in folders inside this element
|
||||
foreach (Folder childFolder in Children.Where(c => c is Folder).Cast<Folder>())
|
||||
layers.AddRange(childFolder.GetAllLayers());
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name for a new layer according to any other layers with a default name similar to creating new folders in
|
||||
/// Explorer
|
||||
/// </summary>
|
||||
/// <returns>The resulting name i.e. <c>New layer</c> or <c>New layer (2)</c></returns>
|
||||
public string GetNewLayerName(string baseName = "New layer")
|
||||
{
|
||||
if (!Children.Any(c => c is Layer && c.Name == baseName))
|
||||
return baseName;
|
||||
|
||||
int current = 2;
|
||||
while (true)
|
||||
{
|
||||
if (Children.Where(c => c is Layer).All(c => c.Name != $"{baseName} ({current})"))
|
||||
return $"{baseName} ({current})";
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name for a new folder according to any other folders with a default name similar to creating new folders
|
||||
/// in Explorer
|
||||
/// </summary>
|
||||
/// <returns>The resulting name i.e. <c>New folder</c> or <c>New folder (2)</c></returns>
|
||||
public string GetNewFolderName(string baseName = "New folder")
|
||||
{
|
||||
if (!Children.Any(c => c is Folder && c.Name == baseName))
|
||||
return baseName;
|
||||
|
||||
int current = 2;
|
||||
while (true)
|
||||
{
|
||||
if (Children.Where(c => c is Folder).All(c => c.Name != $"{baseName} ({current})"))
|
||||
return $"{baseName} ({current})";
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
internal abstract void Load();
|
||||
internal abstract void Save();
|
||||
|
||||
#endregion
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user