diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 261d8170b..31cbebcb3 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -61,6 +61,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumContainsConditionOperator.cs new file mode 100644 index 000000000..dd79f14cb --- /dev/null +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumContainsConditionOperator.cs @@ -0,0 +1,15 @@ +using System; + +namespace Artemis.Core +{ + internal class EnumContainsConditionOperator : ConditionOperator + { + public override string Description => "Contains"; + public override string Icon => "Contain"; + + public override bool Evaluate(Enum a, Enum b) + { + return a != null && b != null && a.HasFlag(b); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumNotContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumNotContainsConditionOperator.cs new file mode 100644 index 000000000..ef78b8470 --- /dev/null +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumNotContainsConditionOperator.cs @@ -0,0 +1,15 @@ +using System; + +namespace Artemis.Core +{ + internal class EnumNotContainsConditionOperator : ConditionOperator + { + public override string Description => "Does not contain"; + public override string Icon => "FormatStrikethrough"; + + public override bool Evaluate(Enum a, Enum b) + { + return a != null && (b == null || !a.HasFlag(b)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs index f5b32c35c..1b683710b 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs @@ -9,7 +9,7 @@ namespace Artemis.Core public override bool Evaluate(string a, string b) { - return a != null && b != null && !a.Contains(b, StringComparison.InvariantCultureIgnoreCase); + return a != null && (b == null || !a.Contains(b, StringComparison.InvariantCultureIgnoreCase)); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 9199aa021..6eea123b0 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; +using Artemis.Core.ScriptingProviders; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; using RGB.NET.Core; @@ -37,6 +38,9 @@ namespace Artemis.Core Profile = Parent.Profile; Name = name; Suspended = false; + Scripts = new List(); + ScriptConfigurations = new List(); + _general = new LayerGeneralProperties(); _transform = new LayerTransformProperties(); @@ -60,6 +64,9 @@ namespace Artemis.Core Profile = profile; Parent = parent; + Scripts = new List(); + ScriptConfigurations = new List(); + _general = new LayerGeneralProperties(); _transform = new LayerTransformProperties(); @@ -75,6 +82,16 @@ namespace Artemis.Core /// public ReadOnlyCollection Leds => _leds.AsReadOnly(); + /// + /// Gets a collection of all active scripts assigned to this layer + /// + public List Scripts { get; } + + /// + /// Gets a collection of all script configurations assigned to this layer + /// + public List ScriptConfigurations { get; } + /// /// Defines the shape that is rendered by the . /// @@ -142,8 +159,10 @@ namespace Artemis.Core if (LayerBrush?.BaseProperties != null) result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties()); foreach (BaseLayerEffect layerEffect in LayerEffects) + { if (layerEffect.BaseProperties != null) result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties()); + } return result; } @@ -171,6 +190,9 @@ namespace Artemis.Core Disposed = true; + while (Scripts.Count > 1) + Scripts[0].Dispose(); + LayerBrushStore.LayerBrushAdded -= LayerBrushStoreOnLayerBrushAdded; LayerBrushStore.LayerBrushRemoved -= LayerBrushStoreOnLayerBrushRemoved; @@ -247,6 +269,11 @@ namespace Artemis.Core ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups); LoadRenderElement(); Adapter.Load(); + + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) + scriptConfiguration.Script?.Dispose(); + ScriptConfigurations.Clear(); + ScriptConfigurations.AddRange(LayerEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); } internal override void Save() @@ -284,6 +311,13 @@ namespace Artemis.Core // Adaption hints Adapter.Save(); + LayerEntity.ScriptConfigurations.Clear(); + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) + { + scriptConfiguration.Save(); + LayerEntity.ScriptConfigurations.Add(scriptConfiguration.Entity); + } + SaveRenderElement(); } @@ -316,6 +350,9 @@ namespace Artemis.Core if (Disposed) throw new ObjectDisposedException("Layer"); + foreach (LayerScript layerScript in Scripts) + layerScript.OnLayerUpdating(deltaTime); + UpdateDisplayCondition(); UpdateTimeline(deltaTime); @@ -323,6 +360,9 @@ namespace Artemis.Core Enable(); else if (Timeline.IsFinished) Disable(); + + foreach (LayerScript layerScript in Scripts) + layerScript.OnLayerUpdated(deltaTime); } /// @@ -473,19 +513,27 @@ namespace Artemis.Core if (LayerBrush == null) throw new ArtemisCoreException("The layer is not yet ready for rendering"); + foreach (LayerScript layerScript in Scripts) + layerScript.OnLayerRendering(canvas, bounds, layerPaint); + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) baseLayerEffect.PreProcess(canvas, bounds, layerPaint); try { canvas.SaveLayer(layerPaint); - - // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off canvas.ClipPath(renderPath); + + // Restore the blend mode before doing the actual render + layerPaint.BlendMode = SKBlendMode.SrcOver; + LayerBrush.InternalRender(canvas, bounds, layerPaint); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) baseLayerEffect.PostProcess(canvas, bounds, layerPaint); + + foreach (LayerScript layerScript in Scripts) + layerScript.OnLayerRendered(canvas, bounds, layerPaint); } finally @@ -500,9 +548,7 @@ namespace Artemis.Core throw new ObjectDisposedException("Layer"); if (!Leds.Any()) - { Path = new SKPath(); - } else { SKPath path = new() {FillType = SKPathFillType.Winding}; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index c9bdf5872..76b787f1d 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Artemis.Core.ScriptingProviders; using Artemis.Storage.Entities.Profile; namespace Artemis.Core @@ -23,6 +24,16 @@ namespace Artemis.Core /// LayerPropertyGroup LayerPropertyGroup { get; } + /// + /// Gets a collection of all active scripts assigned to this layer property + /// + List Scripts { get; } + + /// + /// Gets a collection of all script configurations assigned to this layer property + /// + public List ScriptConfigurations { get; } + /// /// Gets the unique path of the property on the layer /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index d9ad40690..3bbb7f557 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Artemis.Core.ScriptingProviders; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; @@ -30,6 +31,8 @@ namespace Artemis.Core Path = null!; Entity = null!; PropertyDescription = null!; + Scripts = new List(); + ScriptConfigurations = new List(); CurrentValue = default!; DefaultValue = default!; @@ -55,15 +58,15 @@ namespace Artemis.Core /// protected virtual void Dispose(bool disposing) { - if (disposing) - { - _disposed = true; + _disposed = true; - foreach (IDataBinding dataBinding in _dataBindings) - dataBinding.Dispose(); + while (Scripts.Count > 1) + Scripts[0].Dispose(); - Disposed?.Invoke(this, EventArgs.Empty); - } + foreach (IDataBinding dataBinding in _dataBindings) + dataBinding.Dispose(); + + Disposed?.Invoke(this, EventArgs.Empty); } /// @@ -150,6 +153,12 @@ namespace Artemis.Core /// public PropertyDescriptionAttribute PropertyDescription { get; internal set; } + /// + public List Scripts { get; } + + /// + public List ScriptConfigurations { get; } + /// public string Path { get; private set; } @@ -162,12 +171,18 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("LayerProperty"); + foreach (PropertyScript propertyScript in Scripts) + propertyScript.OnPropertyUpdating(timeline.Delta.TotalSeconds); + CurrentValue = BaseValue; UpdateKeyframes(timeline); UpdateDataBindings(timeline); OnUpdated(); + + foreach (PropertyScript propertyScript in Scripts) + propertyScript.OnPropertyUpdated(timeline.Delta.TotalSeconds); } /// @@ -747,6 +762,11 @@ namespace Artemis.Core if (dataBinding != null) _dataBindings.Add(dataBinding); } + + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) + scriptConfiguration.Script?.Dispose(); + ScriptConfigurations.Clear(); + ScriptConfigurations.AddRange(Entity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); } /// @@ -768,6 +788,13 @@ namespace Artemis.Core Entity.DataBindingEntities.Clear(); foreach (IDataBinding dataBinding in _dataBindings) dataBinding.Save(); + + Entity.ScriptConfigurations.Clear(); + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) + { + scriptConfiguration.Save(); + Entity.ScriptConfigurations.Add(scriptConfiguration.Entity); + } } /// diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 3107d0805..75ec5d66b 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Artemis.Core.ScriptingProviders; using Artemis.Storage.Entities.Profile; using SkiaSharp; @@ -13,13 +14,15 @@ namespace Artemis.Core { private readonly object _lock = new(); private bool _isFreshImport; - + internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!) { Configuration = configuration; Profile = this; ProfileEntity = profileEntity; EntityId = profileEntity.Id; + Scripts = new List(); + ScriptConfigurations = new List(); UndoStack = new Stack(); RedoStack = new Stack(); @@ -32,6 +35,17 @@ namespace Artemis.Core /// public ProfileConfiguration Configuration { get; } + /// + /// Gets a collection of all active scripts assigned to this profile + /// + public List Scripts { get; } + + /// + /// Gets a collection of all script configurations assigned to this profile + /// + public List ScriptConfigurations { get; } + + /// /// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it /// since import @@ -62,8 +76,14 @@ namespace Artemis.Core if (Disposed) throw new ObjectDisposedException("Profile"); + foreach (ProfileScript profileScript in Scripts) + profileScript.OnProfileUpdating(deltaTime); + foreach (ProfileElement profileElement in Children) profileElement.Update(deltaTime); + + foreach (ProfileScript profileScript in Scripts) + profileScript.OnProfileUpdated(deltaTime); } } @@ -75,8 +95,14 @@ namespace Artemis.Core if (Disposed) throw new ObjectDisposedException("Profile"); + foreach (ProfileScript profileScript in Scripts) + profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds); + foreach (ProfileElement profileElement in Children) profileElement.Render(canvas, basePosition); + + foreach (ProfileScript profileScript in Scripts) + profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds); } } @@ -125,6 +151,9 @@ namespace Artemis.Core if (!disposing) return; + while (Scripts.Count > 1) + Scripts[0].Dispose(); + foreach (ProfileElement profileElement in Children) profileElement.Dispose(); ChildrenList.Clear(); @@ -157,6 +186,11 @@ namespace Artemis.Core AddChild(new Folder(this, this, rootFolder)); } } + + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) + scriptConfiguration.Script?.Dispose(); + ScriptConfigurations.Clear(); + ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); } internal override void Save() @@ -176,6 +210,13 @@ namespace Artemis.Core ProfileEntity.Layers.Clear(); ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity)); + + ProfileEntity.ScriptConfigurations.Clear(); + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) + { + scriptConfiguration.Save(); + ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity); + } } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index c63bbedef..f8d8a6d47 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -124,8 +124,8 @@ namespace Artemis.Core public void UpdateTimeline(double deltaTime) { // The play mode dictates whether to stick to the main segment unless the display conditions contains events - bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet; - if (DisplayCondition != null && DisplayCondition.ContainsEvents) + bool stickToMainSegment = (Timeline.PlayMode == TimelinePlayMode.Repeat || Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) && DisplayConditionMet; + if (DisplayCondition != null && DisplayCondition.ContainsEvents && Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle) stickToMainSegment = false; Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment); @@ -356,6 +356,7 @@ namespace Artemis.Core private DataModelConditionGroup? _displayCondition; private bool _displayConditionMet; + private bool _toggledOnByEvent = false; /// /// Gets or sets the root display condition group @@ -383,6 +384,9 @@ namespace Artemis.Core return; } + if (Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle) + _toggledOnByEvent = false; + bool conditionMet = DisplayCondition.Evaluate(); if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet) conditionMet = false; @@ -398,25 +402,36 @@ namespace Artemis.Core } else if (conditionMet) { - // Event conditions reset if the timeline finished - if (Timeline.IsFinished) + if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) { - Timeline.JumpToStart(); + _toggledOnByEvent = !_toggledOnByEvent; + if (_toggledOnByEvent) + Timeline.JumpToStart(); } - // and otherwise apply their overlap mode else { - if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart) + // Event conditions reset if the timeline finished + if (Timeline.IsFinished) + { Timeline.JumpToStart(); - else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy) - Timeline.AddExtraTimeline(); - // The third option is ignore which is handled below: + } + // and otherwise apply their overlap mode + else + { + if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart) + Timeline.JumpToStart(); + else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy) + Timeline.AddExtraTimeline(); + // The third option is ignore which is handled below: - // done + // done + } } } - DisplayConditionMet = conditionMet; + DisplayConditionMet = Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle + ? _toggledOnByEvent + : conditionMet; } #endregion diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs index 9fb9a201f..a01b9d232 100644 --- a/src/Artemis.Core/Models/Profile/Timeline.cs +++ b/src/Artemis.Core/Models/Profile/Timeline.cs @@ -155,7 +155,7 @@ namespace Artemis.Core /// /// Gets a boolean indicating whether the timeline has finished its run /// - public bool IsFinished => (Position > Length || Length == TimeSpan.Zero) && !ExtraTimelines.Any(); + public bool IsFinished => Position > Length && !ExtraTimelines.Any(); /// /// Gets a boolean indicating whether the timeline progress has been overridden @@ -516,6 +516,11 @@ namespace Artemis.Core /// /// Play another copy of the timeline on top of the current run /// - Copy + Copy, + + /// + /// Repeat the timeline until the event fires again + /// + Toggle } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs b/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs index aa9a5c3cd..87ee34783 100644 --- a/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs +++ b/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using Artemis.Core.Services; -using Ninject; namespace Artemis.Core.Modules { @@ -13,8 +12,6 @@ namespace Artemis.Core.Modules /// public class ProcessActivationRequirement : IModuleActivationRequirement { - private readonly IProcessMonitorService _processMonitorService; - /// /// Creates a new instance of the class /// @@ -25,11 +22,6 @@ namespace Artemis.Core.Modules if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location)) throw new ArgumentNullException($"Atleast one {nameof(processName)} and {nameof(location)} must not be null"); - // Let's not make a habit out of this :P - if (CoreService.Kernel == null) - throw new ArtemisCoreException("Cannot create a ProcessActivationRequirement before initializing the Core"); - _processMonitorService = CoreService.Kernel.Get(); - ProcessName = processName; Location = location; } @@ -44,13 +36,15 @@ namespace Artemis.Core.Modules /// public string? Location { get; set; } + internal static IProcessMonitorService? ProcessMonitorService { get; set; } + /// public bool Evaluate() { - if (ProcessName == null && Location == null) + if (ProcessMonitorService == null || ProcessName == null && Location == null) return false; - IEnumerable processes = _processMonitorService.GetRunningProcesses(); + IEnumerable processes = ProcessMonitorService.GetRunningProcesses(); if (ProcessName != null) processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase)); if (Location != null) diff --git a/src/Artemis.Core/Plugins/ScriptingProviders/ScriptConfiguration.cs b/src/Artemis.Core/Plugins/ScriptingProviders/ScriptConfiguration.cs new file mode 100644 index 000000000..10095bf4f --- /dev/null +++ b/src/Artemis.Core/Plugins/ScriptingProviders/ScriptConfiguration.cs @@ -0,0 +1,86 @@ +using Artemis.Storage.Entities.General; + +namespace Artemis.Core.ScriptingProviders +{ + /// + /// Represents the configuration of a script + /// + public class ScriptConfiguration : CorePropertyChanged, IStorageModel + { + private string _scriptingProviderId; + private string _name; + private string? _scriptContent; + + /// + /// Creates a new instance of the class + /// + 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(); + } + + /// + /// Gets or sets the ID of the scripting provider + /// + public string ScriptingProviderId + { + get => _scriptingProviderId; + set => SetAndNotify(ref _scriptingProviderId, value); + } + + /// + /// Gets or sets the name of the script + /// + public string Name + { + get => _name; + set => SetAndNotify(ref _name, value); + } + + /// + /// Gets or sets the script's content + /// + public string? ScriptContent + { + get => _scriptContent; + set => SetAndNotify(ref _scriptContent, value); + } + + /// + /// If active, gets the script + /// + public Script? Script { get; internal set; } + + internal ScriptConfigurationEntity Entity { get; } + + #region Implementation of IStorageModel + + /// + public void Load() + { + ScriptingProviderId = Entity.ScriptingProviderId; + ScriptContent = Entity.ScriptContent; + Name = Entity.Name; + } + + /// + public void Save() + { + Entity.ScriptingProviderId = ScriptingProviderId; + Entity.ScriptContent = ScriptContent; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs b/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs new file mode 100644 index 000000000..26baa7362 --- /dev/null +++ b/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Artemis.Core.ScriptingProviders +{ + /// + /// Allows you to implement and register your own scripting provider. + /// + public abstract class ScriptingProvider : ScriptingProvider + where TGlobalScript : GlobalScript + where TProfileScript : ProfileScript + where TLayerScript : LayerScript + where TPropertyScript : PropertyScript + { + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + public abstract IScriptEditorViewModel CreateGlobalScriptEditor(TGlobalScript script); + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + public abstract IScriptEditorViewModel CreateProfileScriptEditor(TProfileScript script); + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + public abstract IScriptEditorViewModel CreateLayerScriptScriptEditor(TLayerScript script); + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + public abstract IScriptEditorViewModel CreatePropertyScriptEditor(TPropertyScript script); + + #region Overrides of ScriptingProvider + + /// + internal override Type GlobalScriptType => typeof(TGlobalScript); + + /// + internal override Type ProfileScriptType => typeof(TProfileScript); + + /// + internal override Type LayerScriptType => typeof(TLayerScript); + + /// + internal override Type PropertyScriptType => typeof(TPropertyScript); + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + 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); + } + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + 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); + } + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + 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); + } + + /// + /// Called when the UI needs a script editor for a + /// + /// The script the editor must edit + 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 + + /// + internal override void InternalDisable() + { + base.InternalDisable(); + + while (Scripts.Count > 0) + Scripts[0].Dispose(); + } + + #endregion + } + + /// + /// Allows you to implement and register your own scripting provider. + /// + /// Note: You can't implement this, implement + /// instead. + /// + /// + 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