using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using SkiaSharp;
namespace Artemis.Core.Modules
{
///
/// Allows you to add support for new games/applications while utilizing Artemis' profile engine and your own data
/// model
///
public abstract class ProfileModule : ProfileModule where T : DataModel
{
///
/// The data model driving this module
/// Note: This default data model is automatically registered upon plugin enable
///
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;
}
///
/// Gets or sets whether this module must also expand the main data model
///
/// Note: If expanding the main data model is all you want your plugin to do, create a
/// plugin instead.
///
///
public bool ExpandsDataModel
{
get => InternalExpandsMainDataModel;
set => InternalExpandsMainDataModel = value;
}
///
/// Override to provide your own data model description. By default this returns a description matching your plugin
/// name and description
///
///
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
}
///
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
///
/// A lambda expression pointing to the property to ignore
public void HideProperty(Expression> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
HiddenPropertiesList.Add(propertyInfo);
}
///
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
/// dm.TimeDataModel.CurrentTimeUTC)
///
/// A lambda expression pointing to the property to stop ignoring
public void ShowProperty(Expression> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
}
internal override void InternalEnable()
{
DataModel = Activator.CreateInstance();
DataModel.Feature = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
internal override void InternalDisable()
{
Deactivate(true);
base.InternalDisable();
}
}
///
/// Allows you to add support for new games/applications while utilizing Artemis' profile engine
///
public abstract class ProfileModule : Module
{
///
/// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y)
///
protected internal readonly List HiddenPropertiesList = new();
private readonly object _lock = new();
///
/// Creates a new instance of the class
///
protected ProfileModule()
{
OpacityOverride = 1;
}
///
/// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y)
///
public ReadOnlyCollection HiddenProperties => HiddenPropertiesList.AsReadOnly();
///
/// Gets the currently active profile
///
public Profile? ActiveProfile { get; private set; }
///
/// Disables updating the profile, rendering does continue
///
public bool IsProfileUpdatingDisabled { get; set; }
///
/// Overrides the opacity of the root folder
///
public double OpacityOverride { get; set; }
///
/// Indicates whether or not a profile change is being animated
///
public bool AnimatingProfileChange { get; private set; }
///
/// Called after the profile has updated
///
/// Time in seconds since the last update
public virtual void ProfileUpdated(double deltaTime)
{
}
///
/// Called after the profile has rendered
///
/// Time since the last render
///
///
public virtual void ProfileRendered(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
{
}
internal override void InternalUpdate(double deltaTime)
{
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);
}
internal override void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
{
Render(deltaTime, canvas, canvasInfo);
lock (_lock)
{
// Render the profile
ActiveProfile?.Render(canvas);
}
ProfileRendered(deltaTime, canvas, canvasInfo);
}
internal async Task ChangeActiveProfileAnimated(Profile? profile, IEnumerable 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 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);
}
#region Events
///
/// Occurs when the has changed
///
public event EventHandler? ActiveProfileChanged;
///
/// Invokes the event
///
protected virtual void OnActiveProfileChanged()
{
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}