mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +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
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an attribute that marks a data model property to be ignored by the UI
|
||||
/// </summary>
|
||||
public class DataModelIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an attribute that describes a data model property
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class DataModelPropertyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the user-friendly name for this property, shown in the UI.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user-friendly description for this property, shown in the UI.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the an optional prefix to show before displaying elements in the UI.
|
||||
/// </summary>
|
||||
public string? Prefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional affix to show behind displaying elements in the UI.
|
||||
/// </summary>
|
||||
public string? Affix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional maximum value, this value is not enforced but used for percentage calculations.
|
||||
/// </summary>
|
||||
public object? MaxValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional minimum value, this value is not enforced but used for percentage calculations.
|
||||
/// </summary>
|
||||
public object? MinValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this property resets the max depth of the data model, defaults to true
|
||||
/// </summary>
|
||||
public bool ResetsDepth { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
using Module = Artemis.Core.Modules.Module;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data model that contains information on a game/application etc.
|
||||
/// </summary>
|
||||
public abstract class DataModel
|
||||
{
|
||||
private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModel" /> class
|
||||
/// </summary>
|
||||
protected DataModel()
|
||||
{
|
||||
// These are both set right after construction to keep the constructor of inherited classes clean
|
||||
Module = null!;
|
||||
DataModelDescription = null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module this data model belongs to
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[DataModelIgnore]
|
||||
public Module Module { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[DataModelIgnore]
|
||||
public DataModelPropertyAttribute DataModelDescription { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the is expansion status indicating whether this data model expands the main data model
|
||||
/// </summary>
|
||||
[DataModelIgnore]
|
||||
public bool IsExpansion { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an read-only dictionary of all dynamic children
|
||||
/// </summary>
|
||||
[DataModelIgnore]
|
||||
public ReadOnlyDictionary<string, DynamicChild> DynamicChildren => new(_dynamicChildren);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a read-only collection of all properties in this datamodel that are to be ignored
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
|
||||
{
|
||||
if (Module is Module module)
|
||||
return module.HiddenProperties;
|
||||
|
||||
return new List<PropertyInfo>().AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a dynamic child has been added to this data model
|
||||
/// </summary>
|
||||
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a dynamic child has been removed from this data model
|
||||
/// </summary>
|
||||
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DynamicChildAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
|
||||
{
|
||||
DynamicChildAdded?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DynamicChildRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
|
||||
{
|
||||
DynamicChildRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#region Dynamic children
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dynamic child to this data model
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the child, must be unique to this data model</param>
|
||||
/// <param name="initialValue">The initial value of the dynamic child</param>
|
||||
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
|
||||
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue)
|
||||
{
|
||||
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dynamic child to this data model
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the child, must be unique to this data model</param>
|
||||
/// <param name="initialValue">The initial value of the dynamic child</param>
|
||||
/// <param name="name">A human readable name for your dynamic child, shown in the UI</param>
|
||||
/// <param name="description">An optional description, shown in the UI</param>
|
||||
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
|
||||
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, string name, string? description = null)
|
||||
{
|
||||
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute {Name = name, Description = description});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dynamic child to this data model
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the child, must be unique to this data model</param>
|
||||
/// <param name="initialValue">The initial value of the dynamic child</param>
|
||||
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
|
||||
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
|
||||
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, DataModelPropertyAttribute attribute)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
if (initialValue == null) throw new ArgumentNullException(nameof(initialValue));
|
||||
if (attribute == null) throw new ArgumentNullException(nameof(attribute));
|
||||
if (key.Contains('.'))
|
||||
throw new ArtemisCoreException("The provided key contains an illegal character (.)");
|
||||
if (_dynamicChildren.ContainsKey(key))
|
||||
{
|
||||
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
|
||||
"because the key is already in use on by another dynamic property this data model.");
|
||||
}
|
||||
|
||||
if (GetType().GetProperty(key) != null)
|
||||
{
|
||||
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
|
||||
"because the key is already in use by a static property on this data model.");
|
||||
}
|
||||
|
||||
// Make sure a name is on the attribute or funny things might happen
|
||||
attribute.Name ??= key.Humanize();
|
||||
if (initialValue is DataModel dynamicDataModel)
|
||||
{
|
||||
dynamicDataModel.Module = Module;
|
||||
dynamicDataModel.DataModelDescription = attribute;
|
||||
}
|
||||
|
||||
DynamicChild<T> dynamicChild = new(initialValue, key, attribute);
|
||||
_dynamicChildren.Add(key, dynamicChild);
|
||||
|
||||
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
|
||||
return dynamicChild;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a previously added dynamic child by its key
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the dynamic child</param>
|
||||
public DynamicChild GetDynamicChild(string key)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
return DynamicChildren[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a previously added dynamic child by its key
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
|
||||
/// <param name="key">The key of the dynamic child</param>
|
||||
/// <returns></returns>
|
||||
public DynamicChild<T> GetDynamicChild<T>(string key)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
return (DynamicChild<T>) DynamicChildren[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a previously added dynamic child by its key
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the dynamic child</param>
|
||||
/// <param name="dynamicChild">
|
||||
/// When this method returns, the <see cref="DynamicChild" /> associated with the specified key,
|
||||
/// if the key is found; otherwise, <see langword="null" />. This parameter is passed uninitialized.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public bool TryGetDynamicChild(string key, [MaybeNullWhen(false)] out DynamicChild dynamicChild)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
|
||||
dynamicChild = null;
|
||||
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
|
||||
return false;
|
||||
|
||||
dynamicChild = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a previously added dynamic child by its key
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
|
||||
/// <param name="key">The key of the dynamic child</param>
|
||||
/// <param name="dynamicChild">
|
||||
/// When this method returns, the <see cref="DynamicChild{T}" /> associated with the specified
|
||||
/// key, if the key is found and the type matches; otherwise, <see langword="null" />. This parameter is passed
|
||||
/// uninitialized.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public bool TryGetDynamicChild<T>(string key, [MaybeNullWhen(false)] out DynamicChild<T> dynamicChild)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
|
||||
dynamicChild = null;
|
||||
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
|
||||
return false;
|
||||
if (value is not DynamicChild<T> typedDynamicChild)
|
||||
return false;
|
||||
dynamicChild = typedDynamicChild;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a dynamic child from the data model by its key
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the dynamic child to remove</param>
|
||||
public void RemoveDynamicChildByKey(string key)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
|
||||
return;
|
||||
|
||||
_dynamicChildren.Remove(key);
|
||||
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a dynamic child from this data model
|
||||
/// </summary>
|
||||
/// <param name="dynamicChild">The dynamic data child to remove</param>
|
||||
public void RemoveDynamicChild(DynamicChild dynamicChild)
|
||||
{
|
||||
if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild));
|
||||
List<string> keys = _dynamicChildren.Where(kvp => kvp.Value.BaseValue == dynamicChild).Select(kvp => kvp.Key).ToList();
|
||||
foreach (string key in keys)
|
||||
{
|
||||
_dynamicChildren.Remove(key);
|
||||
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all dynamic children from this data model
|
||||
/// </summary>
|
||||
public void ClearDynamicChildren()
|
||||
{
|
||||
while (_dynamicChildren.Any())
|
||||
RemoveDynamicChildByKey(_dynamicChildren.First().Key);
|
||||
}
|
||||
|
||||
// Used a runtime by data model paths only
|
||||
internal T? GetDynamicChildValue<T>(string key)
|
||||
{
|
||||
if (TryGetDynamicChild(key, out DynamicChild? dynamicChild) && dynamicChild.BaseValue != null)
|
||||
return (T) dynamicChild.BaseValue;
|
||||
return default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.DataModelExpansions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a dynamic child value with its property attribute
|
||||
/// </summary>
|
||||
public class DynamicChild<T> : DynamicChild
|
||||
{
|
||||
internal DynamicChild(T value, string key, DataModelPropertyAttribute attribute) : base(key, attribute, typeof(T))
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current value of the dynamic child
|
||||
/// </summary>
|
||||
public new T Value { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object? GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a dynamic child value with its property attribute
|
||||
/// </summary>
|
||||
public abstract class DynamicChild
|
||||
{
|
||||
internal DynamicChild(string key, DataModelPropertyAttribute attribute, Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
Attribute = attribute ?? throw new ArgumentNullException(nameof(attribute));
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key of the dynamic child
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attribute describing the dynamic child
|
||||
/// </summary>
|
||||
public DataModelPropertyAttribute Attribute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of <see cref="BaseValue" />
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the dynamic child
|
||||
/// </summary>
|
||||
public object? BaseValue => GetValue();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the dynamic child
|
||||
/// </summary>
|
||||
/// <returns>The current value of the dynamic child</returns>
|
||||
protected abstract object? GetValue();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user