1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development' into feature/avalonia

Code cleanup
This commit is contained in:
Robert 2022-05-14 00:14:03 +02:00
parent 6f75683c4d
commit 35f83e58e5
22 changed files with 176 additions and 95 deletions

View File

@ -6,16 +6,12 @@ A large part of this documentation is being generated based on code but over tim
## Plugins
Artemis 2.0 has been developed from the ground up with plugins in mind. This means almost all functionality can be expanded. The following plugin types are currently available and fully implemented:
- [DataModelExpansion\<T\>](api/Artemis.Core.DataModelExpansions.DataModelExpansion-1.html)
- [DeviceProvider](api/Artemis.Core.DeviceProviders.DeviceProvider.html)
- [LayerBrush\<T\>](api/Artemis.Core.LayerBrushes.LayerBrush-1.html)
- [PerLedLayerBrush\<T\>](api/Artemis.Core.LayerBrushes.PerLedLayerBrush-1.html)
- [RgbNetLayerBrush\<T\>](api/Artemis.Core.LayerBrushes.RgbNetLayerBrush-1.html)
- [LayerEffect](api/Artemis.Core.LayerEffects.LayerEffect-1.html)
- [Module](api/Artemis.Core.Modules.Module.html)
- [Module\<T\>](api/Artemis.Core.Modules.Module-1.html)
- [ProfileModule](api/Artemis.Core.Modules.ProfileModule.html),
- [ProfileModule\<T\>](api/Artemis.Core.Modules.ProfileModule-1.html)
These allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates).
@ -25,6 +21,6 @@ Example implementations of these plugins can be found on [GitHub](https://github
Artemis provides plugins with an API through a range of services.
All the services are available to plugins by using dependency injection in your plugin's constructor. Dependency injection is also available for the different view models plugins may provide.
- [Core Services](api/Artemis.Core.Services.Interfaces.html)
- [UI Services](api/Artemis.UI.Shared.Services.Interfaces.html)
- [Core Services](api/Artemis.Core.Services.html#interfaces)
- [UI Services](api/Artemis.UI.Shared.Services.html#interfaces)

View File

@ -154,6 +154,27 @@
"Ninject": "3.3.3"
}
},
"RGB.NET.Core": {
"type": "Transitive",
"resolved": "1.0.0-prerelease.32",
"contentHash": "HlhhikrkV7OQIRszn6RT6+6N4QGGHaN6NDHK1YssZEJTtE2MIdeRajeQFFziPc4OVOvMgxY13unw/v25r/YALw=="
},
"RGB.NET.Layout": {
"type": "Transitive",
"resolved": "1.0.0-prerelease.32",
"contentHash": "O7I8zR5Hy+joSLCJW10lkIPNMDS1gXe8hQuwm+T5DEDDpykVyc4tnO55WlO5GDmDCS/cXhcwq+qNpLez5Th4Xw==",
"dependencies": {
"RGB.NET.Core": "1.0.0-prerelease.32"
}
},
"RGB.NET.Presets": {
"type": "Transitive",
"resolved": "1.0.0-prerelease.32",
"contentHash": "tqPUA/KUG1hw5pT4D7OLXI6buXxpUIUYNET90gHnVXbadYi0C0OJZ703XDsaPpj4Db8/B2k8f1T4tM4s2KCJhw==",
"dependencies": {
"RGB.NET.Core": "1.0.0-prerelease.32"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1091,7 +1112,39 @@
}
},
"artemis.core": {
"type": "Project"
"type": "Project",
"dependencies": {
"Artemis.Storage": "1.0.0",
"EmbedIO": "3.4.3",
"HidSharp": "2.1.0",
"Humanizer.Core": "2.11.10",
"LiteDB": "5.0.10",
"McMaster.NETCore.Plugins": "1.4.0",
"Newtonsoft.Json": "13.0.1",
"Ninject": "3.3.4",
"Ninject.Extensions.ChildKernel": "3.3.0",
"Ninject.Extensions.Conventions": "3.3.0",
"RGB.NET.Core": "1.0.0-prerelease.32",
"RGB.NET.Layout": "1.0.0-prerelease.32",
"RGB.NET.Presets": "1.0.0-prerelease.32",
"Serilog": "2.10.0",
"Serilog.Sinks.Console": "4.0.0",
"Serilog.Sinks.Debug": "2.0.0",
"Serilog.Sinks.File": "5.0.0",
"SkiaSharp": "2.80.3",
"System.Buffers": "4.5.1",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Numerics.Vectors": "4.5.0",
"System.Reflection.Metadata": "5.0.0",
"System.ValueTuple": "4.5.0"
}
},
"artemis.storage": {
"type": "Project",
"dependencies": {
"LiteDB": "5.0.10",
"Serilog": "2.10.0"
}
}
}
}

View File

@ -50,7 +50,7 @@ namespace Artemis.Core
/// <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)
public static bool IsGenericType(this Type? type, Type genericType)
{
if (type == null)
return false;
@ -95,17 +95,7 @@ namespace Artemis.Core
/// <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
|| value is byte
|| value is short
|| value is ushort
|| value is int
|| value is uint
|| value is long
|| value is ulong
|| value is float
|| value is double
|| value is decimal;
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
@ -199,10 +189,10 @@ namespace Artemis.Core
/// <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 _);
return typeToCheck.IsOfGenericType(genericType, out Type? _);
}
private static bool IsOfGenericType(this Type typeToCheck, Type genericType, out Type concreteGenericType)
private static bool IsOfGenericType(this Type? typeToCheck, Type genericType, out Type? concreteGenericType)
{
while (true)
{

View File

@ -73,9 +73,8 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
}
else
{
List<SKColor> colors = this.Select(c => c.Color).ToList();
for (int i = 0; i <= timesToRepeat; i++)
result.AddRange(colors);
result.AddRange(this.Select(c => c.Color));
}
if (seamless && !IsSeamless())
@ -87,9 +86,7 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
/// <summary>
/// Gets all the positions in the color gradient
/// </summary>
/// <param name="timesToRepeat">
/// The amount of times to repeat the positions
/// </param>
/// <param name="timesToRepeat">The amount of times to repeat the positions</param>
/// <param name="seamless">
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
/// last color
@ -120,7 +117,7 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
// Compress current points evenly
float compression = 1f - 1f / result.Count;
for (int index = 0; index < result.Count; index++)
result[index] = result[index] * compression;
result[index] *= compression;
// Add one extra point at the end
result.Add(1f);
}
@ -132,19 +129,30 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
/// Gets a color at any position between 0.0 and 1.0 using interpolation
/// </summary>
/// <param name="position">A position between 0.0 and 1.0</param>
public SKColor GetColor(float position)
/// <param name="timesToRepeat">The amount of times to repeat the positions</param>
/// <param name="seamless">
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
/// last color
/// </param>
public SKColor GetColor(float position, int timesToRepeat = 0, bool seamless = false)
{
if (!this.Any())
return new SKColor(255, 255, 255);
ColorGradientStop[] stops = this.ToArray();
if (position <= 0) return stops[0].Color;
if (position >= 1) return stops[^1].Color;
ColorGradientStop left = stops[0];
ColorGradientStop? right = null;
foreach (ColorGradientStop stop in stops)
SKColor[] colors = GetColorsArray(timesToRepeat, seamless);
float[] stops = GetPositionsArray(timesToRepeat, seamless);
// If at or over the edges, return the corresponding edge
if (position <= 0) return colors[0];
if (position >= 1) return colors[^1];
// Walk through the stops until we find the one at or after the requested position, that becomes the right stop
// The left stop is the previous stop before the right one was found.
float left = stops[0];
float? right = null;
foreach (float stop in stops)
{
if (stop.Position >= position)
if (stop >= position)
{
right = stop;
break;
@ -153,14 +161,22 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
left = stop;
}
if (right == null || left == right)
return left.Color;
// Get the left stop's color
SKColor leftColor = colors[Array.IndexOf(stops, left)];
position = (float) Math.Round((position - left.Position) / (right.Position - left.Position), 2);
byte a = (byte) ((right.Color.Alpha - left.Color.Alpha) * position + left.Color.Alpha);
byte r = (byte) ((right.Color.Red - left.Color.Red) * position + left.Color.Red);
byte g = (byte) ((right.Color.Green - left.Color.Green) * position + left.Color.Green);
byte b = (byte) ((right.Color.Blue - left.Color.Blue) * position + left.Color.Blue);
// If no right stop was found or the left and right stops are on the same spot, return the left stop's color
if (right == null || left == right)
return leftColor;
// Get the right stop's color
SKColor rightColor = colors[Array.IndexOf(stops, right)];
// Interpolate the position between the left and right color
position = MathF.Round((position - left) / (right.Value - left), 2);
byte a = (byte) ((rightColor.Alpha - leftColor.Alpha) * position + leftColor.Alpha);
byte r = (byte) ((rightColor.Red - leftColor.Red) * position + leftColor.Red);
byte g = (byte) ((rightColor.Green - leftColor.Green) * position + leftColor.Green);
byte b = (byte) ((rightColor.Blue - leftColor.Blue) * position + leftColor.Blue);
return new SKColor(r, g, b, a);
}
@ -561,7 +577,8 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
}
public bool IsFixedSize { get; }
/// <inheritdoc />
public bool IsFixedSize => false;
/// <inheritdoc />
public ColorGradientStop this[int index]

View File

@ -4,14 +4,26 @@ using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// Represents a condition that is always true.
/// </summary>
public class AlwaysOnCondition : ICondition
{
/// <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;

View File

@ -34,6 +34,9 @@ public interface ICondition : IDisposable, IStorageModel
/// </summary>
void UpdateTimeline(double deltaTime);
/// <summary>
/// Overrides the timeline to the provided <paramref name="position"/> as the display condition sees fit.
/// </summary>
void OverrideTimeline(TimeSpan position);
}

View File

@ -4,14 +4,26 @@ using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// Represents a condition that plays once when its script evaluates to <see langword="true"/>.
/// </summary>
public class PlayOnceCondition : ICondition
{
/// <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;

View File

@ -30,6 +30,7 @@ namespace Artemis.Core
protected LayerPropertyGroup()
{
// These are set right after construction to keep the constructor (and inherited constructs) clean
ProfileElement = null!;
GroupDescription = null!;
Path = "";

View File

@ -11,7 +11,6 @@ namespace Artemis.Core.LayerEffects
{
private ILayerEffectConfigurationDialog? _configurationDialog;
private LayerEffectDescriptor _descriptor;
private bool _suspended;
private bool _hasBeenRenamed;
private string _name;
private int _order;

View File

@ -3,8 +3,20 @@ using Artemis.Core.SkiaSharp;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a class that can provide one or more graphics <see cref="IManagedGraphicsContext"/> instances by name.
/// </summary>
public interface IGraphicsContextProvider
{
/// <summary>
/// Gets a read only collection containing the names of all the graphics contexts supported by this provider.
/// </summary>
IReadOnlyCollection<string> GraphicsContextNames { get; }
/// <summary>
/// Gets a managed graphics context by name.
/// </summary>
/// <param name="name">The name of the graphics context.</param>
/// <returns>If found, an instance of the managed graphics context with the given <paramref name="name"/>; otherwise <see langword="null"/>.</returns>
IManagedGraphicsContext? GetGraphicsContext(string name);
}

View File

@ -85,7 +85,7 @@ namespace Artemis.Core.Services
}
}
private void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
private void ActivationUpdateTimerOnElapsed(object? sender, ElapsedEventArgs e)
{
UpdateModuleActivation();
}

View File

@ -24,10 +24,14 @@ namespace Artemis.Core.Services
List<LayerBrushDescriptor> GetLayerBrushes();
/// <summary>
/// Returns the descriptor of the default layer brush
/// Returns the descriptor of the default layer brush
/// </summary>
LayerBrushDescriptor? GetDefaultLayerBrush();
/// <summary>
/// Applies the configured default brush to the provided <paramref name="layer" />.
/// </summary>
/// <param name="layer">The layer to apply the default brush to.</param>
void ApplyDefaultBrush(Layer layer);
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
@ -13,9 +13,6 @@
<DocumentationFile>bin\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Remove="Artemis.UI.Avalonia.Shared.csproj.DotSettings" />
<None Remove="Artemis.UI.Shared.csproj.DotSettings" />
<None Remove="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />

View File

@ -12,6 +12,9 @@ namespace Artemis.UI.Shared.Behaviors;
/// </summary>
public class LostFocusNumberBoxBindingBehavior : Behavior<NumberBox>
{
/// <summary>
/// Gets or sets the value of the binding.
/// </summary>
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, double>(
nameof(Value), defaultBindingMode: BindingMode.TwoWay);
@ -20,6 +23,9 @@ public class LostFocusNumberBoxBindingBehavior : Behavior<NumberBox>
ValueProperty.Changed.Subscribe(e => ((LostFocusNumberBoxBindingBehavior) e.Sender).OnBindingValueChanged());
}
/// <summary>
/// Gets or sets the value of the binding.
/// </summary>
public double Value
{
get => GetValue(ValueProperty);

View File

@ -12,6 +12,9 @@ namespace Artemis.UI.Shared.Behaviors;
/// </summary>
public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
{
/// <summary>
/// Gets or sets the value of the binding.
/// </summary>
public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, string>(
"Text", defaultBindingMode: BindingMode.TwoWay);
@ -20,6 +23,9 @@ public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
TextProperty.Changed.Subscribe(e => ((LostFocusTextBoxBindingBehavior) e.Sender).OnBindingValueChanged());
}
/// <summary>
/// Gets or sets the value of the binding.
/// </summary>
public string Text
{
get => GetValue(TextProperty);

View File

@ -13,6 +13,9 @@ namespace Artemis.UI.Shared.Behaviors;
/// </summary>
public class SliderPointerReleasedBindingBehavior : Behavior<Slider>
{
/// <summary>
/// Gets or sets the value of the binding.
/// </summary>
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, double>(
nameof(Value), defaultBindingMode: BindingMode.TwoWay);
@ -21,6 +24,9 @@ public class SliderPointerReleasedBindingBehavior : Behavior<Slider>
ValueProperty.Changed.Subscribe(e => ((SliderPointerReleasedBindingBehavior) e.Sender).OnBindingValueChanged());
}
/// <summary>
/// Gets or sets the value of the binding.
/// </summary>
public double Value
{
get => GetValue(ValueProperty);

View File

@ -24,7 +24,7 @@ namespace Artemis.UI.Shared
private readonly ObservableCollection<(Enum, string)> _currentValues = new();
private ComboBox? _enumComboBox;
private Type _currentType;
private Type? _currentType;
/// <summary>
/// Creates a new instance of the <see cref="EnumComboBox" /> class.

View File

@ -70,6 +70,9 @@ public class GradientPicker : TemplatedControl
private ColorGradient? _lastColorGradient;
private ColorPicker? _colorPicker;
/// <summary>
/// Creates a new instance of the <see cref="GradientPicker"/> class.
/// </summary>
public GradientPicker()
{
_deleteStop = ReactiveCommand.Create<ColorGradientStop>(s =>

View File

@ -127,14 +127,14 @@ public class GradientPickerColorStop : TemplatedControl
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || !ReferenceEquals(e.Pointer.Captured, this) || PositionReference == null)
{
if (_draggingStop != ColorStop)
if (!Equals(_draggingStop, ColorStop))
return;
_dragOffset = e.GetCurrentPoint(PositionReference).Position.X - GetPixelPosition();
}
double position = e.GetCurrentPoint(PositionReference).Position.X - _dragOffset;
ColorStop.Position = MathF.Round((float) Math.Clamp(position / PositionReference.Bounds.Width, 0, 1), 3, MidpointRounding.AwayFromZero);
ColorStop.Position = MathF.Round((float) Math.Clamp(position / PositionReference?.Bounds.Width ?? 0, 0, 1), 3, MidpointRounding.AwayFromZero);
e.Handled = true;
}

View File

@ -1,36 +0,0 @@
using System;
using System.Windows.Input;
namespace Artemis.UI.Shared;
/// <summary>
/// Represents a placeholder command that does nothing and can't be executed.
/// </summary>
public class NullCommand : ICommand
{
private static readonly Lazy<NullCommand> _instance = new(() => new NullCommand());
private NullCommand()
{
}
/// <summary>
/// Gets the static instance of this command.
/// </summary>
public static ICommand Instance => _instance.Value;
/// <inheritdoc />
public event EventHandler? CanExecuteChanged;
/// <inheritdoc />
public void Execute(object? parameter)
{
throw new InvalidOperationException("NullCommand cannot be executed");
}
/// <inheritdoc />
public bool CanExecute(object? parameter)
{
return false;
}
}

View File

@ -9,8 +9,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
public class ChangeConditionType : IProfileEditorCommand, IDisposable
{
private readonly RenderProfileElement _profileElement;
private readonly ICondition? _condition;
private readonly ICondition? _oldCondition;
private readonly ICondition _condition;
private readonly ICondition _oldCondition;
private bool _executed;
/// <summary>
@ -18,7 +18,7 @@ public class ChangeConditionType : IProfileEditorCommand, IDisposable
/// </summary>
/// <param name="profileElement">The profile element whose condition type to change.</param>
/// <param name="condition">The new condition type.</param>
public ChangeConditionType(RenderProfileElement profileElement, ICondition? condition)
public ChangeConditionType(RenderProfileElement profileElement, ICondition condition)
{
_profileElement = profileElement;
_condition = condition;

View File

@ -8,8 +8,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// </summary>
public class ChangeElementDisplayCondition : IProfileEditorCommand, IDisposable
{
private readonly ICondition? _condition;
private readonly ICondition? _oldCondition;
private readonly ICondition _condition;
private readonly ICondition _oldCondition;
private readonly RenderProfileElement _profileElement;
private bool _executed;
@ -18,7 +18,7 @@ public class ChangeElementDisplayCondition : IProfileEditorCommand, IDisposable
/// </summary>
/// <param name="profileElement">The render profile element whose display condition to change.</param>
/// <param name="condition">The new display condition.</param>
public ChangeElementDisplayCondition(RenderProfileElement profileElement, ICondition? condition)
public ChangeElementDisplayCondition(RenderProfileElement profileElement, ICondition condition)
{
_profileElement = profileElement;
_condition = condition;