mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 11:26:27 +00:00
Profiles - Reworked profile system
Sidebar - Redesigned sidebar with customizable categories Profiles - Added the ability to configure custom profile icons Profiles - Added the ability to activate multiple profiles for modules at once Profiles - Added the ability to create profiles for no modules Profiles - Added the ability to suspend a profile or an entire category Profiles - Added profile activation conditions Profiles - Added file-based importing/exporting Profile editor - Condensed UI, removed tabs Profile editor - Disable condition operators until a left-side is picked
This commit is contained in:
1 parent
4152c290d2
commit
ceeaa4bf6d
164 files changed
+5019
-4220
No files matched your search
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expand the application-wide datamodel
|
||||
/// </summary>
|
||||
public abstract class DataModelExpansion<T> : BaseDataModelExpansion where T : DataModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The main data model of this data model expansion
|
||||
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
|
||||
/// </summary>
|
||||
public T DataModel
|
||||
{
|
||||
get => InternalDataModel as T ?? throw new InvalidOperationException("Internal datamodel does not match the type of the data model");
|
||||
internal set => InternalDataModel = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
|
||||
/// </summary>
|
||||
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
|
||||
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||
{
|
||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
|
||||
HiddenPropertiesList.Add(propertyInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
|
||||
/// dm.TimeDataModel.CurrentTimeUTC)
|
||||
/// </summary>
|
||||
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
|
||||
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||
{
|
||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
|
||||
}
|
||||
|
||||
internal override void InternalEnable()
|
||||
{
|
||||
DataModel = Activator.CreateInstance<T>();
|
||||
DataModel.Feature = this;
|
||||
DataModel.DataModelDescription = GetDataModelDescription();
|
||||
base.InternalEnable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
/// <summary>
|
||||
/// For internal use only, to implement your own layer property type, extend <see cref="DataModelExpansion{T}" />
|
||||
/// instead.
|
||||
/// </summary>
|
||||
public abstract class BaseDataModelExpansion : DataModelPluginFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
||||
|
||||
internal DataModel? InternalDataModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called each frame when the data model should update
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
||||
public abstract void Update(double deltaTime);
|
||||
|
||||
internal void InternalUpdate(double deltaTime)
|
||||
{
|
||||
if (InternalDataModel != null)
|
||||
Update(deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
||||
/// name and description
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
||||
{
|
||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an feature of a certain type provided by a plugin with support for data models
|
||||
/// </summary>
|
||||
public abstract class DataModelPluginFeature : PluginFeature
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using SkiaSharp;
|
||||
namespace Artemis.Core.LayerBrushes
|
||||
{
|
||||
/// <summary>
|
||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="RgbNetLayerBrush{T}" /> or instead
|
||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
|
||||
/// </summary>
|
||||
public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable
|
||||
{
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core.LayerBrushes
|
||||
{
|
||||
/// <summary>
|
||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="RgbNetLayerBrush{T}" /> or instead
|
||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
|
||||
/// </summary>
|
||||
public abstract class PropertiesLayerBrush<T> : BaseLayerBrush where T : LayerPropertyGroup
|
||||
{
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Services;
|
||||
using Ninject;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.LayerBrushes
|
||||
{
|
||||
/// <summary>
|
||||
/// An RGB.NET brush that uses RGB.NET's per-LED rendering engine.
|
||||
/// <para>Note: This brush type always renders on top of regular brushes</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class RgbNetLayerBrush<T> : PropertiesLayerBrush<T> where T : LayerPropertyGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="RgbNetLayerBrush{T}" /> class
|
||||
/// </summary>
|
||||
protected RgbNetLayerBrush()
|
||||
{
|
||||
BrushType = LayerBrushType.RgbNet;
|
||||
SupportsTransformation = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LED group this layer effect is applied to
|
||||
/// </summary>
|
||||
public ListLedGroup? LedGroup { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only, is public for dependency injection but ignore pl0x
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public IRgbService? RgbService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when Artemis needs an instance of the RGB.NET effect you are implementing
|
||||
/// </summary>
|
||||
/// <returns>Your RGB.NET effect</returns>
|
||||
public abstract IBrush GetBrush();
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (RgbService == null)
|
||||
throw new ArtemisCoreException("Cannot dispose RGB.NET layer brush because RgbService is not set");
|
||||
|
||||
Layer.RenderPropertiesUpdated -= LayerOnRenderPropertiesUpdated;
|
||||
LedGroup?.Detach();
|
||||
LedGroup = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal void UpdateLedGroup()
|
||||
{
|
||||
if (LedGroup == null)
|
||||
return;
|
||||
|
||||
if (Layer.Parent != null)
|
||||
LedGroup.ZIndex = Layer.Parent.Children.Count - Layer.Parent.Children.IndexOf(Layer);
|
||||
else
|
||||
LedGroup.ZIndex = 1;
|
||||
|
||||
List<Led> missingLeds = Layer.Leds.Where(l => !LedGroup.ContainsLed(l.RgbLed)).Select(l => l.RgbLed).ToList();
|
||||
List<Led> extraLeds = LedGroup.Where(l => Layer.Leds.All(layerLed => layerLed.RgbLed != l)).ToList();
|
||||
LedGroup.AddLeds(missingLeds);
|
||||
LedGroup.RemoveLeds(extraLeds);
|
||||
LedGroup.Brush = GetBrush();
|
||||
}
|
||||
|
||||
internal override void Initialize()
|
||||
{
|
||||
if (RgbService == null)
|
||||
throw new ArtemisCoreException("Cannot initialize RGB.NET layer brush because RgbService is not set");
|
||||
|
||||
LedGroup = new ListLedGroup(RgbService.Surface);
|
||||
Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated;
|
||||
|
||||
InitializeProperties();
|
||||
UpdateLedGroup();
|
||||
}
|
||||
|
||||
// Not used in this effect type
|
||||
internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint)
|
||||
{
|
||||
throw new NotImplementedException("RGB.NET layer effects do not implement InternalRender");
|
||||
}
|
||||
|
||||
private void LayerOnRenderPropertiesUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateLedGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+8
-9
@@ -7,6 +7,7 @@ using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
using Module = Artemis.Core.Modules.Module;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
@@ -23,16 +24,16 @@ namespace Artemis.Core.DataModelExpansions
|
||||
protected DataModel()
|
||||
{
|
||||
// These are both set right after construction to keep the constructor of inherited classes clean
|
||||
Feature = null!;
|
||||
Module = null!;
|
||||
DataModelDescription = null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature this data model belongs to
|
||||
/// Gets the module this data model belongs to
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[DataModelIgnore]
|
||||
public DataModelPluginFeature Feature { get; internal set; }
|
||||
public Module Module { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
|
||||
@@ -59,11 +60,9 @@ namespace Artemis.Core.DataModelExpansions
|
||||
/// <returns></returns>
|
||||
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
|
||||
{
|
||||
if (Feature is ProfileModule profileModule)
|
||||
return profileModule.HiddenProperties;
|
||||
if (Feature is BaseDataModelExpansion dataModelExpansion)
|
||||
return dataModelExpansion.HiddenProperties;
|
||||
|
||||
if (Module is Module module)
|
||||
return module.HiddenProperties;
|
||||
|
||||
return new List<PropertyInfo>().AsReadOnly();
|
||||
}
|
||||
|
||||
@@ -149,7 +148,7 @@ namespace Artemis.Core.DataModelExpansions
|
||||
attribute.Name ??= key.Humanize();
|
||||
if (initialValue is DataModel dynamicDataModel)
|
||||
{
|
||||
dynamicDataModel.Feature = Feature;
|
||||
dynamicDataModel.Module = Module;
|
||||
dynamicDataModel.DataModelDescription = attribute;
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -1,21 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Storage.Entities.Module;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Modules
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to add support for new games/applications while utilizing your own data model
|
||||
/// Allows you to add new data to the Artemis data model
|
||||
/// </summary>
|
||||
public abstract class Module<T> : Module where T : DataModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The data model driving this module
|
||||
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
|
||||
/// <para>Note: This default data model is automatically registered and instantiated upon plugin enable</para>
|
||||
/// </summary>
|
||||
public T DataModel
|
||||
{
|
||||
@@ -24,43 +25,61 @@ namespace Artemis.Core.Modules
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this module must also expand the main data model
|
||||
/// <para>
|
||||
/// Note: If expanding the main data model is all you want your plugin to do, create a
|
||||
/// <see cref="DataModelExpansion{T}" /> plugin instead.
|
||||
/// </para>
|
||||
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
|
||||
/// </summary>
|
||||
public bool ExpandsDataModel
|
||||
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
|
||||
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||
{
|
||||
get => InternalExpandsMainDataModel;
|
||||
set => InternalExpandsMainDataModel = value;
|
||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
|
||||
HiddenPropertiesList.Add(propertyInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
||||
/// name and description
|
||||
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
|
||||
/// dm.TimeDataModel.CurrentTimeUTC)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
||||
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
|
||||
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||
{
|
||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
|
||||
}
|
||||
|
||||
internal override void InternalEnable()
|
||||
{
|
||||
DataModel = Activator.CreateInstance<T>();
|
||||
DataModel.Feature = this;
|
||||
DataModel.Module = this;
|
||||
DataModel.DataModelDescription = GetDataModelDescription();
|
||||
base.InternalEnable();
|
||||
}
|
||||
|
||||
internal override void InternalDisable()
|
||||
{
|
||||
Deactivate(true);
|
||||
base.InternalDisable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to add support for new games/applications
|
||||
/// For internal use only, please use <see cref="Module{T}" />.
|
||||
/// </summary>
|
||||
public abstract class Module : DataModelPluginFeature
|
||||
public abstract class Module : PluginFeature
|
||||
{
|
||||
private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new();
|
||||
private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of default profile paths
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The modules display name that's shown in the menu
|
||||
/// </summary>
|
||||
@@ -107,35 +126,23 @@ namespace Artemis.Core.Modules
|
||||
public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default priority category for this module, defaults to
|
||||
/// <see cref="ModulePriorityCategory.Normal" />
|
||||
/// Gets or sets a boolean indicating whether this module is always available to profiles or only when profiles
|
||||
/// specifically target this module.
|
||||
/// <para>Note: If set to <see langword="true" />, <see cref="ActivationRequirements" /> are not evaluated.</para>
|
||||
/// </summary>
|
||||
public ModulePriorityCategory DefaultPriorityCategory { get; set; } = ModulePriorityCategory.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current priority category of this module
|
||||
/// </summary>
|
||||
public ModulePriorityCategory PriorityCategory { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current priority of this module within its priority category
|
||||
/// </summary>
|
||||
public int Priority { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of custom module tabs that show in the UI
|
||||
/// </summary>
|
||||
public IEnumerable<ModuleTab>? ModuleTabs { get; protected set; }
|
||||
public bool IsAlwaysAvailable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether updating this module is currently allowed
|
||||
/// </summary>
|
||||
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
|
||||
|
||||
internal DataModel? InternalDataModel { get; set; }
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
||||
|
||||
internal bool InternalExpandsMainDataModel { get; set; }
|
||||
internal ModuleSettingsEntity? SettingsEntity { get; set; }
|
||||
internal DataModel? InternalDataModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called each frame when the module should update
|
||||
@@ -143,14 +150,6 @@ namespace Artemis.Core.Modules
|
||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
||||
public abstract void Update(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Called each frame when the module should render
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Time since the last render</param>
|
||||
/// <param name="canvas"></param>
|
||||
/// <param name="canvasInfo"></param>
|
||||
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <see cref="ActivationRequirements" /> are met or during an override
|
||||
/// </summary>
|
||||
@@ -158,7 +157,9 @@ namespace Artemis.Core.Modules
|
||||
/// If true, the activation was due to an override. This usually means the module was activated
|
||||
/// by the profile editor
|
||||
/// </param>
|
||||
public abstract void ModuleActivated(bool isOverride);
|
||||
public virtual void ModuleActivated(bool isOverride)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <see cref="ActivationRequirements" /> are no longer met or during an override
|
||||
@@ -167,7 +168,9 @@ namespace Artemis.Core.Modules
|
||||
/// If true, the deactivation was due to an override. This usually means the module was deactivated
|
||||
/// by the profile editor
|
||||
/// </param>
|
||||
public abstract void ModuleDeactivated(bool isOverride);
|
||||
public virtual void ModuleDeactivated(bool isOverride)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the activation requirements following the <see cref="ActivationRequirementMode" /> and returns the result
|
||||
@@ -185,6 +188,50 @@ namespace Artemis.Core.Modules
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
||||
/// name and description
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
||||
{
|
||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a default profile by reading it from the file found at the provided path
|
||||
/// </summary>
|
||||
/// <param name="category">The category in which to place the default profile</param>
|
||||
/// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
|
||||
/// already in the list.
|
||||
/// </returns>
|
||||
protected bool AddDefaultProfile(DefaultCategoryName category, string file)
|
||||
{
|
||||
// It can be null if the plugin has not loaded yet in which case Plugin.ResolveRelativePath fails
|
||||
if (Plugin == null!)
|
||||
{
|
||||
if (_pendingDefaultProfilePaths.Contains((category, file)))
|
||||
return false;
|
||||
_pendingDefaultProfilePaths.Add((category, file));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Path.IsPathRooted(file))
|
||||
file = Plugin.ResolveRelativePath(file);
|
||||
|
||||
// Ensure the file exists
|
||||
if (!File.Exists(file))
|
||||
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
|
||||
|
||||
if (_defaultProfilePaths.Contains((category, file)))
|
||||
return false;
|
||||
_defaultProfilePaths.Add((category, file));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal virtual void InternalUpdate(double deltaTime)
|
||||
{
|
||||
StartUpdateMeasure();
|
||||
@@ -193,13 +240,6 @@ namespace Artemis.Core.Modules
|
||||
StopUpdateMeasure();
|
||||
}
|
||||
|
||||
internal virtual void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
||||
{
|
||||
StartRenderMeasure();
|
||||
Render(deltaTime, canvas, canvasInfo);
|
||||
StopRenderMeasure();
|
||||
}
|
||||
|
||||
internal virtual void Activate(bool isOverride)
|
||||
{
|
||||
if (IsActivated)
|
||||
@@ -220,6 +260,20 @@ namespace Artemis.Core.Modules
|
||||
ModuleDeactivated(isOverride);
|
||||
}
|
||||
|
||||
#region Overrides of PluginFeature
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void InternalEnable()
|
||||
{
|
||||
foreach ((DefaultCategoryName categoryName, var path) in _pendingDefaultProfilePaths)
|
||||
AddDefaultProfile(categoryName, path);
|
||||
_pendingDefaultProfilePaths.Clear();
|
||||
|
||||
base.InternalEnable();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal virtual void Reactivate(bool isDeactivateOverride, bool isActivateOverride)
|
||||
{
|
||||
if (!IsActivated)
|
||||
@@ -228,16 +282,6 @@ namespace Artemis.Core.Modules
|
||||
Deactivate(isDeactivateOverride);
|
||||
Activate(isActivateOverride);
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
{
|
||||
if (SettingsEntity == null)
|
||||
SettingsEntity = new ModuleSettingsEntity();
|
||||
|
||||
SettingsEntity.ModuleId = Id;
|
||||
SettingsEntity.PriorityCategory = (int) PriorityCategory;
|
||||
SettingsEntity.Priority = Priority;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -255,25 +299,4 @@ namespace Artemis.Core.Modules
|
||||
/// </summary>
|
||||
All
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the priority category of a module
|
||||
/// </summary>
|
||||
public enum ModulePriorityCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a normal render priority
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the module renders for a specific application/game, rendering on top of normal modules
|
||||
/// </summary>
|
||||
Application,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the module renders an overlay, always rendering on top
|
||||
/// </summary>
|
||||
Overlay
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.Modules
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ModuleTab<T> : ModuleTab where T : IModuleViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ModuleTab{T}" /> class
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the tab</param>
|
||||
public ModuleTab(string title) : base(title)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a UI tab for a specific module
|
||||
/// </summary>
|
||||
public abstract class ModuleTab
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ModuleTab" /> class
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the tab</param>
|
||||
protected ModuleTab(string title)
|
||||
{
|
||||
Title = title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The title of the tab
|
||||
/// </summary>
|
||||
public string Title { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of view model the tab contains
|
||||
/// </summary>
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Modules
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to add support for new games/applications while utilizing Artemis' profile engine and your own data
|
||||
/// model
|
||||
/// </summary>
|
||||
public abstract class ProfileModule<T> : ProfileModule where T : DataModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The data model driving this module
|
||||
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
|
||||
/// </summary>
|
||||
public T DataModel
|
||||
{
|
||||
get => InternalDataModel as T ?? throw new InvalidOperationException("Internal datamodel does not match the type of the data model");
|
||||
internal set => InternalDataModel = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this module must also expand the main data model
|
||||
/// <para>
|
||||
/// Note: If expanding the main data model is all you want your plugin to do, create a
|
||||
/// <see cref="BaseDataModelExpansion" /> plugin instead.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool ExpandsDataModel
|
||||
{
|
||||
get => InternalExpandsMainDataModel;
|
||||
set => InternalExpandsMainDataModel = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
||||
/// name and description
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
||||
{
|
||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
|
||||
/// </summary>
|
||||
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
|
||||
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||
{
|
||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
|
||||
HiddenPropertiesList.Add(propertyInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
|
||||
/// dm.TimeDataModel.CurrentTimeUTC)
|
||||
/// </summary>
|
||||
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
|
||||
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||
{
|
||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
|
||||
}
|
||||
|
||||
internal override void InternalEnable()
|
||||
{
|
||||
DataModel = Activator.CreateInstance<T>();
|
||||
DataModel.Feature = this;
|
||||
DataModel.DataModelDescription = GetDataModelDescription();
|
||||
base.InternalEnable();
|
||||
}
|
||||
|
||||
internal override void InternalDisable()
|
||||
{
|
||||
Deactivate(true);
|
||||
base.InternalDisable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to add support for new games/applications while utilizing Artemis' profile engine
|
||||
/// </summary>
|
||||
public abstract class ProfileModule : Module
|
||||
{
|
||||
private readonly List<string> _defaultProfilePaths = new();
|
||||
private readonly List<string> _pendingDefaultProfilePaths = new();
|
||||
private readonly List<ProfileEntity> _defaultProfiles = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ProfileModule" /> class
|
||||
/// </summary>
|
||||
protected ProfileModule()
|
||||
{
|
||||
OpacityOverride = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently active profile
|
||||
/// </summary>
|
||||
public Profile? ActiveProfile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disables updating the profile, rendering does continue
|
||||
/// </summary>
|
||||
public bool IsProfileUpdatingDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the opacity of the root folder
|
||||
/// </summary>
|
||||
public double OpacityOverride { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether or not a profile change is being animated
|
||||
/// </summary>
|
||||
public bool AnimatingProfileChange { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of default profiles, to add a new default profile use <see cref="AddDefaultProfile" />
|
||||
/// </summary>
|
||||
internal ReadOnlyCollection<ProfileEntity> DefaultProfiles => _defaultProfiles.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Called after the profile has updated
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
||||
public virtual void ProfileUpdated(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the profile has rendered
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Time since the last render</param>
|
||||
/// <param name="canvas"></param>
|
||||
/// <param name="canvasInfo"></param>
|
||||
public virtual void ProfileRendered(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="ActiveProfile" /> has changed
|
||||
/// </summary>
|
||||
public event EventHandler? ActiveProfileChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a default profile by reading it from the file found at the provided path
|
||||
/// </summary>
|
||||
/// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
|
||||
/// already in the list.
|
||||
/// </returns>
|
||||
protected bool AddDefaultProfile(string file)
|
||||
{
|
||||
// It can be null if the plugin has not loaded yet...
|
||||
if (Plugin == null!)
|
||||
{
|
||||
if (_pendingDefaultProfilePaths.Contains(file))
|
||||
return false;
|
||||
_pendingDefaultProfilePaths.Add(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Path.IsPathRooted(file))
|
||||
file = Plugin.ResolveRelativePath(file);
|
||||
|
||||
if (_defaultProfilePaths.Contains(file))
|
||||
return false;
|
||||
_defaultProfilePaths.Add(file);
|
||||
|
||||
// Ensure the file exists
|
||||
if (!File.Exists(file))
|
||||
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
|
||||
// Deserialize and make sure that succeeded
|
||||
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(File.ReadAllText(file), ProfileService.ExportSettings);
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisPluginFeatureException(this, $"Failed to deserialize default profile at {file}.");
|
||||
// Ensure the profile ID is unique
|
||||
if (_defaultProfiles.Any(d => d.Id == profileEntity.Id))
|
||||
throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {profileEntity.Id} already in use.");
|
||||
|
||||
profileEntity.IsFreshImport = true;
|
||||
profileEntity.IsActive = false;
|
||||
_defaultProfiles.Add(profileEntity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ActiveProfileChanged" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnActiveProfileChanged()
|
||||
{
|
||||
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal override void InternalEnable()
|
||||
{
|
||||
foreach (string pendingDefaultProfile in _pendingDefaultProfilePaths)
|
||||
AddDefaultProfile(pendingDefaultProfile);
|
||||
_pendingDefaultProfilePaths.Clear();
|
||||
|
||||
base.InternalEnable();
|
||||
}
|
||||
|
||||
internal override void InternalUpdate(double deltaTime)
|
||||
{
|
||||
StartUpdateMeasure();
|
||||
if (IsUpdateAllowed)
|
||||
Update(deltaTime);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
OpacityOverride = AnimatingProfileChange
|
||||
? Math.Max(0, OpacityOverride - 0.1)
|
||||
: Math.Min(1, OpacityOverride + 0.1);
|
||||
|
||||
// Update the profile
|
||||
if (!IsProfileUpdatingDisabled)
|
||||
ActiveProfile?.Update(deltaTime);
|
||||
}
|
||||
|
||||
ProfileUpdated(deltaTime);
|
||||
StopUpdateMeasure();
|
||||
}
|
||||
|
||||
internal override void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
||||
{
|
||||
StartRenderMeasure();
|
||||
Render(deltaTime, canvas, canvasInfo);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
// Render the profile
|
||||
ActiveProfile?.Render(canvas, SKPointI.Empty);
|
||||
}
|
||||
|
||||
ProfileRendered(deltaTime, canvas, canvasInfo);
|
||||
StopRenderMeasure();
|
||||
}
|
||||
|
||||
internal async Task ChangeActiveProfileAnimated(Profile? profile, IEnumerable<ArtemisDevice> devices)
|
||||
{
|
||||
if (profile != null && profile.Module != this)
|
||||
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}.");
|
||||
if (!IsActivated)
|
||||
throw new ArtemisCoreException("Cannot activate a profile on a deactivated module");
|
||||
|
||||
if (profile == ActiveProfile || AnimatingProfileChange)
|
||||
return;
|
||||
|
||||
AnimatingProfileChange = true;
|
||||
|
||||
while (OpacityOverride > 0)
|
||||
await Task.Delay(50);
|
||||
|
||||
ChangeActiveProfile(profile, devices);
|
||||
AnimatingProfileChange = false;
|
||||
|
||||
while (OpacityOverride < 1)
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
internal void ChangeActiveProfile(Profile? profile, IEnumerable<ArtemisDevice> devices)
|
||||
{
|
||||
if (profile != null && profile.Module != this)
|
||||
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}.");
|
||||
if (!IsActivated)
|
||||
throw new ArtemisCoreException("Cannot activate a profile on a deactivated module");
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (profile == ActiveProfile)
|
||||
return;
|
||||
|
||||
ActiveProfile?.Dispose();
|
||||
|
||||
ActiveProfile = profile;
|
||||
ActiveProfile?.Activate(devices);
|
||||
}
|
||||
|
||||
OnActiveProfileChanged();
|
||||
}
|
||||
|
||||
internal override void Deactivate(bool isOverride)
|
||||
{
|
||||
base.Deactivate(isOverride);
|
||||
|
||||
Profile? profile = ActiveProfile;
|
||||
ActiveProfile = null;
|
||||
profile?.Dispose();
|
||||
}
|
||||
|
||||
internal override void Reactivate(bool isDeactivateOverride, bool isActivateOverride)
|
||||
{
|
||||
if (!IsActivated)
|
||||
return;
|
||||
|
||||
// Avoid disposing the profile
|
||||
base.Deactivate(isDeactivateOverride);
|
||||
Activate(isActivateOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,10 @@ namespace Artemis.Core
|
||||
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
||||
|
||||
if (Icon != null) return;
|
||||
if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType))
|
||||
Icon = "TableAdd";
|
||||
else if (typeof(DeviceProvider).IsAssignableFrom(featureType))
|
||||
if (typeof(DeviceProvider).IsAssignableFrom(featureType))
|
||||
Icon = "Devices";
|
||||
else if (typeof(ProfileModule).IsAssignableFrom(featureType))
|
||||
Icon = "VectorRectangle";
|
||||
else if (typeof(Module).IsAssignableFrom(featureType))
|
||||
Icon = "GearBox";
|
||||
Icon = "VectorRectangle";
|
||||
else if (typeof(LayerBrushProvider).IsAssignableFrom(featureType))
|
||||
Icon = "Brush";
|
||||
else if (typeof(LayerEffectProvider).IsAssignableFrom(featureType))
|
||||
@@ -66,10 +62,8 @@ namespace Artemis.Core
|
||||
if (Icon != null) return;
|
||||
Icon = Instance switch
|
||||
{
|
||||
BaseDataModelExpansion => "TableAdd",
|
||||
DeviceProvider => "Devices",
|
||||
ProfileModule => "VectorRectangle",
|
||||
Module => "GearBox",
|
||||
Module => "VectorRectangle",
|
||||
LayerBrushProvider => "Brush",
|
||||
LayerEffectProvider => "AutoAwesome",
|
||||
_ => "Plugin"
|
||||
|
||||
Reference in new issue
Block a user