mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 11:26:27 +00:00
Scripting - Core implementation (#629)
Scripting - Added plugin classes Layers - Fix certain blend modes not working as intended UI - Add customizable header per page Profile editor - Hide the regular header Profile editor - Added a new toolbar
This commit is contained in:
1 parent
d749ab5bbd
commit
048864fe78
47 files changed
+1599
-237
No files matched your search
@@ -0,0 +1,86 @@
|
||||
using Artemis.Storage.Entities.General;
|
||||
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the configuration of a script
|
||||
/// </summary>
|
||||
public class ScriptConfiguration : CorePropertyChanged, IStorageModel
|
||||
{
|
||||
private string _scriptingProviderId;
|
||||
private string _name;
|
||||
private string? _scriptContent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ScriptConfiguration" /> class
|
||||
/// </summary>
|
||||
public ScriptConfiguration(ScriptingProvider provider, string name)
|
||||
{
|
||||
ScriptingProviderId = provider.Id;
|
||||
Name = name;
|
||||
Entity = new ScriptConfigurationEntity();
|
||||
}
|
||||
|
||||
internal ScriptConfiguration(ScriptConfigurationEntity entity)
|
||||
{
|
||||
ScriptingProviderId = null!;
|
||||
Name = null!;
|
||||
Entity = entity;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the scripting provider
|
||||
/// </summary>
|
||||
public string ScriptingProviderId
|
||||
{
|
||||
get => _scriptingProviderId;
|
||||
set => SetAndNotify(ref _scriptingProviderId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the script
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetAndNotify(ref _name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the script's content
|
||||
/// </summary>
|
||||
public string? ScriptContent
|
||||
{
|
||||
get => _scriptContent;
|
||||
set => SetAndNotify(ref _scriptContent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If active, gets the script
|
||||
/// </summary>
|
||||
public Script? Script { get; internal set; }
|
||||
|
||||
internal ScriptConfigurationEntity Entity { get; }
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
ScriptingProviderId = Entity.ScriptingProviderId;
|
||||
ScriptContent = Entity.ScriptContent;
|
||||
Name = Entity.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Entity.ScriptingProviderId = ScriptingProviderId;
|
||||
Entity.ScriptContent = ScriptContent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to implement and register your own scripting provider.
|
||||
/// </summary>
|
||||
public abstract class ScriptingProvider<TGlobalScript, TProfileScript, TLayerScript, TPropertyScript> : ScriptingProvider
|
||||
where TGlobalScript : GlobalScript
|
||||
where TProfileScript : ProfileScript
|
||||
where TLayerScript : LayerScript
|
||||
where TPropertyScript : PropertyScript
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="GlobalScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreateGlobalScriptEditor(TGlobalScript script);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="ProfileScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreateProfileScriptEditor(TProfileScript script);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="LayerScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreateLayerScriptScriptEditor(TLayerScript script);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="PropertyScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreatePropertyScriptEditor(TPropertyScript script);
|
||||
|
||||
#region Overrides of ScriptingProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override Type GlobalScriptType => typeof(TGlobalScript);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override Type ProfileScriptType => typeof(TProfileScript);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override Type LayerScriptType => typeof(TLayerScript);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override Type PropertyScriptType => typeof(TPropertyScript);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="GlobalScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public override IScriptEditorViewModel CreateGlobalScriptEditor(GlobalScript script)
|
||||
{
|
||||
if (script == null) throw new ArgumentNullException(nameof(script));
|
||||
if (script.GetType() != GlobalScriptType)
|
||||
throw new ArtemisCoreException($"This scripting provider only supports global scripts of type {GlobalScriptType.Name}");
|
||||
|
||||
return CreateGlobalScriptEditor((TGlobalScript) script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="ProfileScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public override IScriptEditorViewModel CreateProfileScriptEditor(ProfileScript script)
|
||||
{
|
||||
if (script == null) throw new ArgumentNullException(nameof(script));
|
||||
if (script.GetType() != ProfileScriptType)
|
||||
throw new ArtemisCoreException($"This scripting provider only supports profile scripts of type {ProfileScriptType.Name}");
|
||||
|
||||
return CreateProfileScriptEditor((TProfileScript) script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="LayerScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public override IScriptEditorViewModel CreateLayerScriptScriptEditor(LayerScript script)
|
||||
{
|
||||
if (script == null) throw new ArgumentNullException(nameof(script));
|
||||
if (script.GetType() != LayerScriptType)
|
||||
throw new ArtemisCoreException($"This scripting provider only supports layer scripts of type {LayerScriptType.Name}");
|
||||
|
||||
return CreateLayerScriptScriptEditor((TLayerScript) script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="PropertyScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public override IScriptEditorViewModel CreatePropertyScriptEditor(PropertyScript script)
|
||||
{
|
||||
if (script == null) throw new ArgumentNullException(nameof(script));
|
||||
if (script.GetType() != PropertyScriptType)
|
||||
throw new ArtemisCoreException($"This scripting provider only supports property scripts of type {PropertyScriptType.Name}");
|
||||
|
||||
return CreatePropertyScriptEditor((TPropertyScript) script);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of PluginFeature
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void InternalDisable()
|
||||
{
|
||||
base.InternalDisable();
|
||||
|
||||
while (Scripts.Count > 0)
|
||||
Scripts[0].Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to implement and register your own scripting provider.
|
||||
/// <para>
|
||||
/// Note: You can't implement this, implement
|
||||
/// <see cref="ScriptingProvider{TProfileScript,TLayerScript,TPropertyScript,TGlobalScript}" /> instead.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class ScriptingProvider : PluginFeature
|
||||
{
|
||||
internal abstract Type GlobalScriptType { get; }
|
||||
internal abstract Type PropertyScriptType { get; }
|
||||
internal abstract Type LayerScriptType { get; }
|
||||
internal abstract Type ProfileScriptType { get; }
|
||||
internal List<Script> InternalScripts { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all active scripts belonging to this scripting provider
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Script> Scripts => InternalScripts.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="GlobalScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreateGlobalScriptEditor(GlobalScript script);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="ProfileScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreateProfileScriptEditor(ProfileScript script);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="LayerScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreateLayerScriptScriptEditor(LayerScript script);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the UI needs a script editor for a <see cref="PropertyScript" />
|
||||
/// </summary>
|
||||
/// <param name="script">The script the editor must edit</param>
|
||||
public abstract IScriptEditorViewModel CreatePropertyScriptEditor(PropertyScript script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model containing a script editor
|
||||
/// </summary>
|
||||
public interface IScriptEditorViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the script this editor is editing
|
||||
/// </summary>
|
||||
Script Script { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Artemis.Core.Services;
|
||||
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a script running globally
|
||||
/// </summary>
|
||||
public abstract class GlobalScript : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected GlobalScript(ScriptConfiguration configuration) : base(configuration)
|
||||
{
|
||||
}
|
||||
|
||||
internal ScriptingService ScriptingService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the Artemis Core is about to update
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnCoreUpdating(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the Artemis Core has been updated
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnCoreUpdated(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
#region Overrides of Script
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void InternalCleanup()
|
||||
{
|
||||
ScriptingService.InternalGlobalScripts.Remove(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a script bound to a specific <see cref="Layer" /> processed by a <see cref="ScriptingProvider" />.
|
||||
/// </summary>
|
||||
public abstract class LayerScript : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected LayerScript(Layer layer, ScriptConfiguration configuration) : base(configuration)
|
||||
{
|
||||
Layer = layer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer this script is bound to
|
||||
/// </summary>
|
||||
public Layer Layer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the layer is about to update
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnLayerUpdating(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the layer has been updated
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnLayerUpdated(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the layer is about to render
|
||||
/// </summary>
|
||||
/// <param name="canvas">The layer canvas</param>
|
||||
/// <param name="bounds">The area to be filled, covers the shape</param>
|
||||
/// <param name="paint">The paint to be used to fill the shape</param>
|
||||
public virtual void OnLayerRendering(SKCanvas canvas, SKRect bounds, SKPaint paint)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the layer has been rendered
|
||||
/// </summary>
|
||||
/// <param name="canvas">The layer canvas</param>
|
||||
/// <param name="bounds">The area to be filled, covers the shape</param>
|
||||
/// <param name="paint">The paint to be used to fill the shape</param>
|
||||
public virtual void OnLayerRendered(SKCanvas canvas, SKRect bounds, SKPaint paint)
|
||||
{
|
||||
}
|
||||
|
||||
#region Overrides of Script
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void InternalCleanup()
|
||||
{
|
||||
lock (Layer.Scripts)
|
||||
{
|
||||
Layer.Scripts.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a script bound to a specific <see cref="Profile" /> processed by a <see cref="ScriptingProvider" />.
|
||||
/// </summary>
|
||||
public abstract class ProfileScript : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected ProfileScript(Profile profile, ScriptConfiguration configuration) : base(configuration)
|
||||
{
|
||||
Profile = profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile this script is bound to
|
||||
/// </summary>
|
||||
public Profile Profile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the profile is about to update
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnProfileUpdating(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the profile has been updated
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnProfileUpdated(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the profile is about to render
|
||||
/// </summary>
|
||||
/// <param name="canvas">The profile canvas</param>
|
||||
/// <param name="bounds">The area to be filled, covers the entire canvas</param>
|
||||
public virtual void OnProfileRendering(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the profile has been rendered
|
||||
/// </summary>
|
||||
/// <param name="canvas">The profile canvas</param>
|
||||
/// <param name="bounds">The area to be filled, covers the entire canvas</param>
|
||||
public virtual void OnProfileRendered(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
}
|
||||
|
||||
#region Overrides of Script
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void InternalCleanup()
|
||||
{
|
||||
lock (Profile.Scripts)
|
||||
{
|
||||
Profile.Scripts.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a script bound to a specific <see cref="LayerProperty{T}" /> processed by a
|
||||
/// <see cref="ScriptingProvider" />.
|
||||
/// </summary>
|
||||
public abstract class PropertyScript : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected PropertyScript(ILayerProperty layerProperty, ScriptConfiguration configuration) : base(configuration)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer property this script is bound to
|
||||
/// </summary>
|
||||
public ILayerProperty LayerProperty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the property is about to update
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnPropertyUpdating(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the property has been updated
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||
public virtual void OnPropertyUpdated(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
#region Overrides of Script
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void InternalCleanup()
|
||||
{
|
||||
lock (LayerProperty.Scripts)
|
||||
{
|
||||
LayerProperty.Scripts.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Artemis.Core.ScriptingProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a script processed by a <see cref="ScriptingProviders.ScriptingProvider" />.
|
||||
/// </summary>
|
||||
public abstract class Script : CorePropertyChanged, IDisposable
|
||||
{
|
||||
private ScriptingProvider _scriptingProvider = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The base constructor of any script
|
||||
/// </summary>
|
||||
/// <param name="configuration">The script configuration this script belongs to</param>
|
||||
protected Script(ScriptConfiguration configuration)
|
||||
{
|
||||
if (configuration.Script != null)
|
||||
throw new ArtemisCoreException("The provided script configuration already has an active script");
|
||||
|
||||
ScriptConfiguration = configuration;
|
||||
ScriptConfiguration.Script = this;
|
||||
|
||||
ScriptConfiguration.PropertyChanged += ScriptConfigurationOnPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scripting provider this script belongs to
|
||||
/// </summary>
|
||||
public ScriptingProvider ScriptingProvider
|
||||
{
|
||||
get => _scriptingProvider;
|
||||
internal set => SetAndNotify(ref _scriptingProvider, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script configuration this script belongs to
|
||||
/// </summary>
|
||||
public ScriptConfiguration ScriptConfiguration { get; }
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void ScriptConfigurationOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(ScriptConfiguration.ScriptContent))
|
||||
OnScriptContentChanged();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
ScriptConfiguration.PropertyChanged -= ScriptConfigurationOnPropertyChanged;
|
||||
ScriptConfiguration.Script = null;
|
||||
ScriptingProvider.InternalScripts.Remove(this);
|
||||
|
||||
// Can't trust those pesky plugin devs!
|
||||
InternalCleanup();
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal abstract void InternalCleanup();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the contents of the script have changed
|
||||
/// </summary>
|
||||
public event EventHandler? ScriptContentChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ScriptContentChanged" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnScriptContentChanged()
|
||||
{
|
||||
ScriptContentChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user