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.Exceptions; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.DataModelExpansions; using Artemis.Core.Plugins.DataModelExpansions.Attributes; using Artemis.Core.Plugins.DataModelExpansions.Internal; using Artemis.Core.Utilities; using SkiaSharp; namespace Artemis.Core.Plugins.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 /// public T DataModel { get => (T) InternalDataModel; 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 DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.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) { var 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) { var propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda); HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo)); } internal override void InternalEnablePlugin() { DataModel = Activator.CreateInstance(); DataModel.PluginInfo = PluginInfo; DataModel.DataModelDescription = GetDataModelDescription(); base.InternalEnablePlugin(); } internal override void InternalDisablePlugin() { DataModel = null; base.InternalDisablePlugin(); } } /// /// Allows you to add support for new games/applications while utilizing Artemis' profile engine /// public abstract class ProfileModule : Module { protected ProfileModule() { OpacityOverride = 1; } protected readonly List HiddenPropertiesList = new List(); /// /// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y) /// public ReadOnlyCollection HiddenProperties => HiddenPropertiesList.AsReadOnly(); public Profile ActiveProfile { get; private set; } /// /// Disables updating the profile, rendering does continue /// public bool IsProfileUpdatingDisabled { get; set; } /// public override void Update(double deltaTime) { lock (this) { OpacityOverride = AnimatingProfileChange ? Math.Max(0, OpacityOverride - 0.1) : Math.Min(1, OpacityOverride + 0.1); // Update the profile if (!IsProfileUpdatingDisabled) ActiveProfile?.Update(deltaTime); } } /// public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo) { lock (this) { // Render the profile ActiveProfile?.Render(deltaTime, canvas, canvasInfo); } } internal async Task ChangeActiveProfileAnimated(Profile profile, ArtemisSurface surface) { if (profile != null && profile.Module != this) throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {PluginInfo}."); 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, surface); AnimatingProfileChange = false; while (OpacityOverride < 1) await Task.Delay(50); } internal void ChangeActiveProfile(Profile profile, ArtemisSurface surface) { if (profile != null && profile.Module != this) throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {PluginInfo}."); if (!IsActivated) throw new ArtemisCoreException("Cannot activate a profile on a deactivated module"); lock (this) { if (profile == ActiveProfile) return; ActiveProfile?.Dispose(); ActiveProfile = profile; ActiveProfile?.Activate(surface); } OnActiveProfileChanged(); } /// /// 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; } internal override void Deactivate(bool isOverride) { base.Deactivate(isOverride); var profile = ActiveProfile; ActiveProfile = null; profile?.Dispose(); } #region Events public event EventHandler ActiveProfileChanged; protected virtual void OnActiveProfileChanged() { ActiveProfileChanged?.Invoke(this, EventArgs.Empty); } #endregion } }