1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'layer-refactor'

This commit is contained in:
SpoinkyNL 2020-09-14 19:22:43 +02:00
commit 447112729d
195 changed files with 4975 additions and 4307 deletions

View File

@ -1,75 +0,0 @@
// Pointers used to auto-update.
// NOTE: If you're going to copy paste these for your own application, include a link to https://github.com/SpoinkyNL/Artemis
[
{
"Game":"RocketLeague",
"GameVersion":"1.30",
"GameAddresses":[
{
"Description":"Boost",
"BasePointer":{
"value":23986356
},
"Offsets":[
196,
24,
904,
1852,
548
]
}
]
},
{
"Game":"WorldOfWarcraft",
"GameVersion":"7.0.3.22810",
"GameAddresses":[
{
"Description":"ObjectManager",
"BasePointer":{
"value":22511728
},
"Offsets":null
},
{
"Description":"LocalPlayer",
"BasePointer":{
"value":23715600
},
"Offsets":null
},
{
"Description":"NameCache",
"BasePointer":{
"value":22142184
},
"Offsets":null
},
{
"Description":"TargetGuid",
"BasePointer":{
"value":24758592
},
"Offsets":null
}
]
},
{
"Game":"Terraria",
"GameVersion":"1.3.4.4",
"GameAddresses":[
{
"Description":"PlayerBase",
"BasePointer":{
"value":3784824
},
"Offsets":[
640,
1728,
1652,
60
]
}
]
}
]

View File

@ -37,6 +37,7 @@
<ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.1.6" />
<PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="LiteDB" Version="5.0.8" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />

View File

@ -1,5 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cplugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cprofiles/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cstores/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models/@EntryIndexedValue">True</s:Boolean>
@ -20,6 +23,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Ctypes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayershapes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=placeholders/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cdatamodelexpansions_005Cattributes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cdatamodelexpansions_005Cinternal/@EntryIndexedValue">True</s:Boolean>
@ -29,6 +33,10 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores_005Cregistrations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -32,7 +32,13 @@ namespace Artemis.Core
/// <summary>
/// The plugin info used by core components of Artemis
/// </summary>
public static readonly PluginInfo CorePluginInfo = new PluginInfo {Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core"};
public static readonly PluginInfo CorePluginInfo = new PluginInfo
{
Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core", Enabled = true
};
internal static readonly CorePlugin CorePlugin = new CorePlugin {PluginInfo = CorePluginInfo};
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new EffectPlaceholderPlugin {PluginInfo = CorePluginInfo};
/// <summary>
/// A read-only collection containing all primitive numeric types

View File

@ -1,14 +0,0 @@
using System;
namespace Artemis.Core
{
public class LayerPropertyEventArgs : EventArgs
{
public LayerPropertyEventArgs(BaseLayerProperty layerProperty)
{
LayerProperty = layerProperty;
}
public BaseLayerProperty LayerProperty { get; }
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace Artemis.Core
{
public class LayerPropertyEventArgs<T> : EventArgs
{
public LayerPropertyEventArgs(LayerProperty<T> layerProperty)
{
LayerProperty = layerProperty;
}
public LayerProperty<T> LayerProperty { get; }
}
public class LayerPropertyEventArgs : EventArgs
{
public LayerPropertyEventArgs(ILayerProperty layerProperty)
{
LayerProperty = layerProperty;
}
public ILayerProperty LayerProperty { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Core
{
internal class ConditionOperatorStoreEvent
{
public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration)
{
Registration = registration;
}
public ConditionOperatorRegistration Registration { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Core
{
internal class DataBindingModifierTypeStoreEvent
{
public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration)
{
TypeRegistration = typeRegistration;
}
public DataBindingModifierTypeRegistration TypeRegistration { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Core
{
internal class DataModelStoreEvent
{
public DataModelStoreEvent(DataModelRegistration registration)
{
Registration = registration;
}
public DataModelRegistration Registration { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Core
{
internal class LayerBrushStoreEvent
{
public LayerBrushStoreEvent(LayerBrushRegistration registration)
{
Registration = registration;
}
public LayerBrushRegistration Registration { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Core
{
internal class LayerEffectStoreEvent
{
public LayerEffectStoreEvent(LayerEffectRegistration registration)
{
Registration = registration;
}
public LayerEffectRegistration Registration { get; }
}
}

View File

@ -0,0 +1,18 @@
namespace Artemis.Core
{
/// <summary>
/// Represents a model that can be loaded and saved to persistent storage
/// </summary>
public interface IStorageModel
{
/// <summary>
/// Loads the model from its associated entity
/// </summary>
void Load();
/// <summary>
/// Saves the model to its associated entity
/// </summary>
void Save();
}
}

View File

@ -0,0 +1,14 @@
namespace Artemis.Core
{
/// <summary>
/// Represents a model that updates using a delta time
/// </summary>
public interface IUpdateModel
{
/// <summary>
/// Performs an update on the model
/// </summary>
/// <param name="deltaTime">The delta time in seconds</param>
void Update(double deltaTime);
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.Abstract;
@ -7,7 +8,7 @@ namespace Artemis.Core
/// <summary>
/// An abstract class for display condition parts
/// </summary>
public abstract class DisplayConditionPart
public abstract class DisplayConditionPart : IDisposable
{
private readonly List<DisplayConditionPart> _children = new List<DisplayConditionPart>();
@ -60,8 +61,25 @@ namespace Artemis.Core
/// <returns></returns>
public abstract bool EvaluateObject(object target);
internal abstract void Initialize(IDataModelService dataModelService);
internal abstract void ApplyToEntity();
internal abstract void Save();
internal abstract DisplayConditionPartEntity GetEntity();
#region IDisposable
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -2,18 +2,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.Services;
namespace Artemis.Core
{
/// <summary>
/// A display condition operator is used by the conditions system to perform a specific boolean check
/// A condition operator is used by the conditions system to perform a specific boolean check
/// </summary>
public abstract class DisplayConditionOperator
public abstract class ConditionOperator
{
private IDataModelService _dataModelService;
private bool _registered;
/// <summary>
/// Gets the plugin info this condition operator belongs to
/// <para>Note: Not set until after registering</para>
@ -57,35 +53,5 @@ namespace Artemis.Core
/// <param name="rightSide">The parameter on the right side of the expression</param>
/// <returns></returns>
public abstract BinaryExpression CreateExpression(Expression leftSide, Expression rightSide);
internal void Register(PluginInfo pluginInfo, IDataModelService dataModelService)
{
if (_registered)
return;
PluginInfo = pluginInfo;
_dataModelService = dataModelService;
if (PluginInfo != Constants.CorePluginInfo)
PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled;
_registered = true;
}
internal void Unsubscribe()
{
if (!_registered)
return;
if (PluginInfo != Constants.CorePluginInfo)
PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled;
_registered = false;
}
private void InstanceOnPluginDisabled(object sender, EventArgs e)
{
// The service will call Unsubscribe
_dataModelService.RemoveConditionOperator(this);
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Linq;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@ -12,6 +11,8 @@ namespace Artemis.Core
/// </summary>
public class DisplayConditionGroup : DisplayConditionPart
{
private bool _disposed;
/// <summary>
/// Creates a new instance of the <see cref="DisplayConditionGroup" /> class
/// </summary>
@ -56,6 +57,9 @@ namespace Artemis.Core
/// <inheritdoc />
public override bool Evaluate()
{
if (_disposed)
throw new ObjectDisposedException("DisplayConditionGroup");
// Empty groups are always true
if (Children.Count == 0)
return true;
@ -81,6 +85,9 @@ namespace Artemis.Core
/// <inheritdoc />
public override bool EvaluateObject(object target)
{
if (_disposed)
throw new ObjectDisposedException("DisplayConditionGroup");
// Empty groups are always true
if (Children.Count == 0)
return true;
@ -98,26 +105,33 @@ namespace Artemis.Core
};
}
internal override void ApplyToEntity()
internal override void Save()
{
Entity.BooleanOperator = (int) BooleanOperator;
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (var child in Children)
child.ApplyToEntity();
}
internal override void Initialize(IDataModelService dataModelService)
{
foreach (var child in Children)
child.Initialize(dataModelService);
child.Save();
}
internal override DisplayConditionPartEntity GetEntity()
{
return Entity;
}
#region IDisposable
protected override void Dispose(bool disposing)
{
_disposed = true;
foreach (var child in Children)
child.Dispose();
base.Dispose(disposing);
}
#endregion
}
public enum BooleanOperator

View File

@ -3,7 +3,6 @@ using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@ -11,10 +10,14 @@ namespace Artemis.Core
{
public class DisplayConditionList : DisplayConditionPart
{
private bool _disposed;
public DisplayConditionList(DisplayConditionPart parent)
{
Parent = parent;
Entity = new DisplayConditionListEntity();
Initialize();
}
public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity)
@ -22,6 +25,8 @@ namespace Artemis.Core
Parent = parent;
Entity = entity;
ListOperator = (ListOperator) entity.ListOperator;
Initialize();
}
public DisplayConditionListEntity Entity { get; set; }
@ -34,6 +39,9 @@ namespace Artemis.Core
public override bool Evaluate()
{
if (_disposed)
throw new ObjectDisposedException("DisplayConditionList");
if (CompiledListAccessor == null)
return false;
@ -42,6 +50,9 @@ namespace Artemis.Core
public override bool EvaluateObject(object target)
{
if (_disposed)
throw new ObjectDisposedException("DisplayConditionList");
if (!Children.Any())
return false;
if (!(target is IList list))
@ -60,6 +71,9 @@ namespace Artemis.Core
public void UpdateList(DataModel dataModel, string path)
{
if (_disposed)
throw new ObjectDisposedException("DisplayConditionList");
if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null)
@ -90,6 +104,9 @@ namespace Artemis.Core
public void CreateExpression()
{
if (_disposed)
throw new ObjectDisposedException("DisplayConditionList");
var parameter = Expression.Parameter(typeof(object), "listDataModel");
var accessor = ListPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, ListDataModel.GetType()),
@ -98,12 +115,15 @@ namespace Artemis.Core
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
CompiledListAccessor = lambda.Compile();
}
internal override void ApplyToEntity()
internal override void Save()
{
// Target list
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
Entity.ListPropertyPath = ListPropertyPath;
if (ListDataModel != null)
{
Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid;
Entity.ListPropertyPath = ListPropertyPath;
}
// Operator
Entity.ListOperator = (int) ListOperator;
@ -112,7 +132,7 @@ namespace Artemis.Core
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (var child in Children)
child.ApplyToEntity();
child.Save();
}
internal override DisplayConditionPartEntity GetEntity()
@ -120,13 +140,15 @@ namespace Artemis.Core
return Entity;
}
internal override void Initialize(IDataModelService dataModelService)
internal void Initialize()
{
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
if (Entity.ListDataModelGuid == null)
return;
// Get the data model and ensure the path is valid
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value);
var dataModel = DataModelStore.Get(Entity.ListDataModelGuid.Value)?.DataModel;
if (dataModel == null || !dataModel.ContainsPath(Entity.ListPropertyPath))
return;
@ -143,9 +165,50 @@ namespace Artemis.Core
Entity.Children.Clear();
AddChild(new DisplayConditionGroup(this));
}
Children[0].Initialize(dataModelService);
}
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.ListDataModelGuid && dataModel.ContainsPath(Entity.ListPropertyPath))
{
ListDataModel = dataModel;
ListPropertyPath = Entity.ListPropertyPath;
CreateExpression();
}
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (ListDataModel != e.Registration.DataModel)
return;
ListDataModel = null;
CompiledListAccessor = null;
}
#endregion
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
foreach (var child in Children)
child.Dispose();
base.Dispose(disposing);
}
#endregion
}
public enum ListOperator

View File

@ -2,7 +2,6 @@
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using Newtonsoft.Json;
@ -18,6 +17,7 @@ namespace Artemis.Core
Entity = new DisplayConditionListPredicateEntity();
ApplyParentList();
Initialize();
}
public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity)
@ -27,12 +27,13 @@ namespace Artemis.Core
PredicateType = (ProfileRightSideType) entity.PredicateType;
ApplyParentList();
Initialize();
}
public DisplayConditionListPredicateEntity Entity { get; set; }
public ProfileRightSideType PredicateType { get; set; }
public DisplayConditionOperator Operator { get; private set; }
public ConditionOperator Operator { get; private set; }
public Type ListType { get; private set; }
public DataModel ListDataModel { get; private set; }
@ -120,9 +121,9 @@ namespace Artemis.Core
CreateExpression();
}
public void UpdateOperator(DisplayConditionOperator displayConditionOperator)
public void UpdateOperator(ConditionOperator conditionOperator)
{
if (displayConditionOperator == null)
if (conditionOperator == null)
{
Operator = null;
return;
@ -130,13 +131,13 @@ namespace Artemis.Core
if (LeftPropertyPath == null)
{
Operator = displayConditionOperator;
Operator = conditionOperator;
return;
}
var leftType = GetTypeAtInnerPath(LeftPropertyPath);
if (displayConditionOperator.SupportsType(leftType))
Operator = displayConditionOperator;
if (conditionOperator.SupportsType(leftType))
Operator = conditionOperator;
CreateExpression();
}
@ -189,22 +190,31 @@ namespace Artemis.Core
return result;
}
internal override void ApplyToEntity()
internal override void Save()
{
Entity.PredicateType = (int) PredicateType;
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
Entity.ListPropertyPath = ListPropertyPath;
if (ListDataModel != null)
{
Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid;
Entity.ListPropertyPath = ListPropertyPath;
}
Entity.LeftPropertyPath = LeftPropertyPath;
Entity.RightPropertyPath = RightPropertyPath;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
Entity.OperatorType = Operator?.GetType().Name;
if (Operator != null)
{
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
Entity.OperatorType = Operator.GetType().Name;
}
}
internal override void Initialize(IDataModelService dataModelService)
private void Initialize()
{
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side
if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath))
UpdateLeftSide(Entity.LeftPropertyPath);
@ -212,7 +222,7 @@ namespace Artemis.Core
// Operator
if (Entity.OperatorPluginGuid != null)
{
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
var conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
@ -221,7 +231,7 @@ namespace Artemis.Core
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPropertyPath != null)
{
if (ListContainsInnerPath(Entity.RightPropertyPath))
UpdateLeftSide(Entity.LeftPropertyPath);
UpdateRightSideDynamic(Entity.RightPropertyPath);
}
// Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
@ -241,7 +251,7 @@ namespace Artemis.Core
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
dataModelService.LogListPredicateDeserializationFailure(this, e);
DeserializationLogger.LogListPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
@ -255,7 +265,7 @@ namespace Artemis.Core
}
catch (JsonException e)
{
dataModelService.LogListPredicateDeserializationFailure(this, e);
DeserializationLogger.LogListPredicateDeserializationFailure(this, e);
}
}
}
@ -383,5 +393,37 @@ namespace Artemis.Core
Expression.Property
);
}
#region Event handlers
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{
var conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator);
}
private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e)
{
if (e.Registration.ConditionOperator != Operator)
return;
Operator = null;
CompiledListPredicate = null;
}
#endregion
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
base.Dispose(disposing);
}
#endregion
}
}

View File

@ -2,7 +2,6 @@
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using Newtonsoft.Json;
@ -25,6 +24,8 @@ namespace Artemis.Core
Parent = parent;
PredicateType = predicateType;
Entity = new DisplayConditionPredicateEntity();
Initialize();
}
/// <summary>
@ -37,6 +38,8 @@ namespace Artemis.Core
Parent = parent;
Entity = entity;
PredicateType = (ProfileRightSideType) entity.PredicateType;
Initialize();
}
/// <summary>
@ -47,7 +50,7 @@ namespace Artemis.Core
/// <summary>
/// Gets the operator
/// </summary>
public DisplayConditionOperator Operator { get; private set; }
public ConditionOperator Operator { get; private set; }
/// <summary>
/// Gets the currently used instance of the left data model
@ -175,11 +178,11 @@ namespace Artemis.Core
/// <summary>
/// Updates the operator of the predicate and re-compiles the expression
/// </summary>
/// <param name="displayConditionOperator"></param>
public void UpdateOperator(DisplayConditionOperator displayConditionOperator)
/// <param name="conditionOperator"></param>
public void UpdateOperator(ConditionOperator conditionOperator)
{
// Calling CreateExpression will clear compiled expressions
if (displayConditionOperator == null)
if (conditionOperator == null)
{
Operator = null;
CreateExpression();
@ -189,18 +192,18 @@ namespace Artemis.Core
// No need to clear compiled expressions, without a left data model they are already null
if (LeftDataModel == null)
{
Operator = displayConditionOperator;
Operator = conditionOperator;
return;
}
var leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath);
if (!displayConditionOperator.SupportsType(leftType))
if (!conditionOperator.SupportsType(leftType))
{
throw new ArtemisCoreException($"Cannot apply operator {displayConditionOperator.GetType().Name} to this predicate because " +
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
$"it does not support left side type {leftType.Name}");
}
Operator = displayConditionOperator;
Operator = conditionOperator;
CreateExpression();
}
@ -229,7 +232,7 @@ namespace Artemis.Core
return $"[Static] {LeftPropertyPath} {Operator.Description} {RightStaticValue}";
}
internal override void ApplyToEntity()
internal override void Save()
{
Entity.PredicateType = (int) PredicateType;
Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
@ -243,12 +246,17 @@ namespace Artemis.Core
Entity.OperatorType = Operator?.GetType().Name;
}
internal override void Initialize(IDataModelService dataModelService)
internal void Initialize()
{
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side
if (Entity.LeftDataModelGuid != null)
{
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.LeftDataModelGuid.Value);
var dataModel = DataModelStore.Get(Entity.LeftDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
}
@ -256,7 +264,7 @@ namespace Artemis.Core
// Operator
if (Entity.OperatorPluginGuid != null)
{
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
var conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
@ -264,7 +272,7 @@ namespace Artemis.Core
// Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightDataModelGuid != null)
{
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.RightDataModelGuid.Value);
var dataModel = DataModelStore.Get(Entity.RightDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, Entity.RightPropertyPath);
}
@ -286,7 +294,7 @@ namespace Artemis.Core
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
dataModelService.LogPredicateDeserializationFailure(this, e);
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
@ -409,17 +417,60 @@ namespace Artemis.Core
);
}
private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter)
{
var listType = dataModel.GetListTypeInPath(path);
if (listType == null)
throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list");
#region Event handlers
path = dataModel.GetListInnerPath(path);
return path.Split('.').Aggregate<string, Expression>(
Expression.Convert(listParameter, listType), // Cast to the appropriate type
Expression.Property
);
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.LeftDataModelGuid && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
if (dataModel.PluginInfo.Guid == Entity.RightDataModelGuid && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, Entity.RightPropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (LeftDataModel == e.Registration.DataModel)
{
CompiledDynamicPredicate = null;
LeftDataModel = null;
}
if (RightDataModel == e.Registration.DataModel)
{
CompiledDynamicPredicate = null;
RightDataModel = null;
}
}
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{
var conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator);
}
private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e)
{
if (e.Registration.ConditionOperator != Operator)
return;
Operator = null;
CompiledStaticPredicate = null;
CompiledDynamicPredicate = null;
}
#endregion
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
base.Dispose(disposing);
}
}
}

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class EqualsConditionOperator : DisplayConditionOperator
internal class EqualsConditionOperator : ConditionOperator
{
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(object)};

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class GreaterThanConditionOperator : DisplayConditionOperator
internal class GreaterThanConditionOperator : ConditionOperator
{
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class GreaterThanOrEqualConditionOperator : DisplayConditionOperator
internal class GreaterThanOrEqualConditionOperator : ConditionOperator
{
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class LessThanConditionOperator : DisplayConditionOperator
internal class LessThanConditionOperator : ConditionOperator
{
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class LessThanOrEqualConditionOperator : DisplayConditionOperator
internal class LessThanOrEqualConditionOperator : ConditionOperator
{
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class NotEqualConditionOperator : DisplayConditionOperator
internal class NotEqualConditionOperator : ConditionOperator
{
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(object)};

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace Artemis.Core
{
internal class StringContainsConditionOperator : DisplayConditionOperator
internal class StringContainsConditionOperator : ConditionOperator
{
private readonly MethodInfo _contains;
private readonly MethodInfo _toLower;

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace Artemis.Core
{
internal class StringEndsWithConditionOperator : DisplayConditionOperator
internal class StringEndsWithConditionOperator : ConditionOperator
{
private readonly MethodInfo _endsWith;
private readonly MethodInfo _toLower;

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace Artemis.Core
{
internal class StringEqualsConditionOperator : DisplayConditionOperator
internal class StringEqualsConditionOperator : ConditionOperator
{
private readonly MethodInfo _toLower;

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace Artemis.Core
{
internal class StringNotContainsConditionOperator : DisplayConditionOperator
internal class StringNotContainsConditionOperator : ConditionOperator
{
private readonly MethodInfo _contains;
private readonly MethodInfo _toLower;

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace Artemis.Core
{
internal class StringNotEqualConditionOperator : DisplayConditionOperator
internal class StringNotEqualConditionOperator : ConditionOperator
{
private readonly MethodInfo _toLower;

View File

@ -4,7 +4,7 @@ using System.Linq.Expressions;
namespace Artemis.Core
{
internal class StringNullConditionOperator : DisplayConditionOperator
internal class StringNullConditionOperator : ConditionOperator
{
public StringNullConditionOperator()
{

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace Artemis.Core
{
internal class StringStartsWithConditionOperator : DisplayConditionOperator
internal class StringStartsWithConditionOperator : ConditionOperator
{
private readonly MethodInfo _startsWith;
private readonly MethodInfo _toLower;

View File

@ -1,48 +1,51 @@
using System;
using SkiaSharp;
namespace Artemis.Core
{
/// <inheritdoc />
public class FloatDataBindingConverter : DataBindingConverter
public class FloatDataBindingConverter : FloatDataBindingConverter<float>
{
}
/// <inheritdoc />
/// <typeparam name="T">The type of layer property this converter is applied to</typeparam>
public class FloatDataBindingConverter<T> : DataBindingConverter<T, float>
{
/// <summary>
/// Creates a new instance of the <see cref="FloatDataBindingConverter{T}" /> class
/// </summary>
public FloatDataBindingConverter()
{
SupportedType = typeof(float);
SupportsSum = true;
SupportsInterpolate = true;
}
/// <inheritdoc />
public override object Sum(object a, object b)
public override float Sum(float a, float b)
{
return Convert.ToSingle(a) + Convert.ToSingle(b);
return a + b;
}
/// <inheritdoc />
public override object Interpolate(object a, object b, double progress)
public override float Interpolate(float a, float b, double progress)
{
var floatA = Convert.ToSingle(a);
var floatB = Convert.ToSingle(b);
var diff = floatB - floatA;
return floatA + diff * progress;
var diff = b - a;
return (float) (a + diff * progress);
}
/// <inheritdoc />
public override void ApplyValue(object value)
public override void ApplyValue(float value)
{
var floatValue = Convert.ToSingle(value);
if (ValueTypeSetExpression == null)
return;
if (DataBinding.LayerProperty.PropertyDescription.MaxInputValue is float max)
floatValue = Math.Min(floatValue, max);
value = Math.Min(value, max);
if (DataBinding.LayerProperty.PropertyDescription.MinInputValue is float min)
floatValue = Math.Max(floatValue, min);
value = Math.Max(value, min);
ValueSetter?.Invoke(floatValue);
}
/// <inheritdoc />
public override object GetValue()
{
return ValueGetter?.Invoke();
base.ApplyValue(value);
}
}
}

View File

@ -3,11 +3,10 @@
namespace Artemis.Core
{
/// <inheritdoc />
public class GeneralDataBindingConverter : DataBindingConverter
public class GeneralDataBindingConverter<T> : DataBindingConverter<T, object> where T : ILayerProperty
{
public GeneralDataBindingConverter()
{
SupportedType = typeof(object);
SupportsSum = false;
SupportsInterpolate = false;
}
@ -23,17 +22,5 @@ namespace Artemis.Core
{
throw new NotSupportedException();
}
/// <inheritdoc />
public override void ApplyValue(object value)
{
ValueSetter?.Invoke(value);
}
/// <inheritdoc />
public override object GetValue()
{
return ValueGetter?.Invoke();
}
}
}

View File

@ -3,11 +3,18 @@
namespace Artemis.Core
{
/// <inheritdoc />
public class IntDataBindingConverter : DataBindingConverter
public class IntDataBindingConverter : IntDataBindingConverter<int>
{
}
/// <inheritdoc />
public class IntDataBindingConverter<T> : DataBindingConverter<T, int>
{
/// <summary>
/// Creates a new instance of the <see cref="IntDataBindingConverter{T}" /> class
/// </summary>
public IntDataBindingConverter()
{
SupportedType = typeof(int);
SupportsSum = true;
SupportsInterpolate = true;
}
@ -19,30 +26,16 @@ namespace Artemis.Core
public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero;
/// <inheritdoc />
public override object Sum(object a, object b)
public override int Sum(int a, int b)
{
return (int) a + (int) b;
return a + b;
}
/// <inheritdoc />
public override object Interpolate(object a, object b, double progress)
public override int Interpolate(int a, int b, double progress)
{
var intA = (int) a;
var intB = (int) b;
var diff = intB - intA;
return (int) Math.Round(intA + diff * progress, InterpolationRoundingMode);
}
/// <inheritdoc />
public override void ApplyValue(object value)
{
ValueSetter?.Invoke(value);
}
/// <inheritdoc />
public override object GetValue()
{
return ValueGetter?.Invoke();
var diff = b - a;
return (int) Math.Round(a + diff * progress, InterpolationRoundingMode);
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using SkiaSharp;
namespace Artemis.Core
{
// This is internal because it's mainly a proof-of-concept
internal class SKColorArgbDataBindingConverter : DataBindingConverter<SKColor, byte>
{
private readonly Channel _channel;
public SKColorArgbDataBindingConverter(Channel channel)
{
_channel = channel;
SupportsSum = true;
SupportsInterpolate = true;
}
public override byte Sum(byte a, byte b)
{
return ClampToByte(a + b);
}
public override byte Interpolate(byte a, byte b, double progress)
{
var diff = b - a;
return ClampToByte(a + diff * progress);
}
public override void ApplyValue(byte value)
{
switch (_channel)
{
case Channel.Alpha:
DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithAlpha(value);
break;
case Channel.Red:
DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithRed(value);
break;
case Channel.Green:
DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithGreen(value);
break;
case Channel.Blue:
DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithBlue(value);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override byte ConvertFromObject(object source)
{
var saveValue = Convert.ToDouble(source);
return ClampToByte(saveValue);
}
private static byte ClampToByte(double value)
{
return (byte) Math.Clamp(value, 0, 255);
}
public enum Channel
{
Alpha,
Red,
Green,
Blue
}
}
}

View File

@ -1,95 +0,0 @@
using System;
using SkiaSharp;
namespace Artemis.Core
{
// This is internal because it's mainly a proof-of-concept
internal class SKColorPartDataBindingConverter : DataBindingConverter
{
private readonly Channel _channel;
public SKColorPartDataBindingConverter(Channel channel)
{
_channel = channel;
SupportsSum = true;
SupportsInterpolate = true;
SupportedType = _channel switch
{
Channel.Alpha => typeof(byte),
Channel.Red => typeof(byte),
Channel.Green => typeof(byte),
Channel.Blue => typeof(byte),
Channel.Hue => typeof(float),
_ => throw new ArgumentOutOfRangeException()
};
}
public override object Sum(object a, object b)
{
return (float) a + (float) b;
}
public override object Interpolate(object a, object b, double progress)
{
var diff = (float) b - (float) a;
return diff * progress;
}
public override void ApplyValue(object value)
{
var property = (SKColorLayerProperty) DataBinding.LayerProperty;
switch (_channel)
{
case Channel.Alpha:
property.CurrentValue = property.CurrentValue.WithAlpha((byte) value);
break;
case Channel.Red:
property.CurrentValue = property.CurrentValue.WithRed((byte) value);
break;
case Channel.Green:
property.CurrentValue = property.CurrentValue.WithGreen((byte) value);
break;
case Channel.Blue:
property.CurrentValue = property.CurrentValue.WithBlue((byte) value);
break;
case Channel.Hue:
property.CurrentValue.ToHsv(out var h, out var s, out var v);
property.CurrentValue = SKColor.FromHsv((float) value, s, v);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override object GetValue()
{
var property = (SKColorLayerProperty) DataBinding.LayerProperty;
switch (_channel)
{
case Channel.Alpha:
return property.CurrentValue.Alpha;
case Channel.Red:
return property.CurrentValue.Red;
case Channel.Green:
return property.CurrentValue.Green;
case Channel.Blue:
return property.CurrentValue.Blue;
case Channel.Hue:
property.CurrentValue.ToHsv(out var h, out _, out _);
return h;
default:
throw new ArgumentOutOfRangeException();
}
}
public enum Channel
{
Alpha,
Red,
Green,
Blue,
Hue
}
}
}

View File

@ -2,60 +2,55 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <summary>
/// A data binding that binds a certain <see cref="BaseLayerProperty" /> to a value inside a <see cref="DataModel" />
/// </summary>
public class DataBinding
/// <inheritdoc />
public class DataBinding<TLayerProperty, TProperty> : IDataBinding
{
private readonly List<DataBindingModifier> _modifiers = new List<DataBindingModifier>();
private readonly List<DataBindingModifier<TLayerProperty, TProperty>> _modifiers = new List<DataBindingModifier<TLayerProperty, TProperty>>();
private object _currentValue;
private object _previousValue;
private TProperty _currentValue;
private bool _disposed;
private TimeSpan _easingProgress;
private TProperty _previousValue;
internal DataBinding(DataBindingRegistration dataBindingRegistration)
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
{
LayerProperty = dataBindingRegistration.LayerProperty;
Entity = new DataBindingEntity();
ApplyRegistration(dataBindingRegistration);
ApplyToEntity();
Save();
Initialize();
}
internal DataBinding(BaseLayerProperty layerProperty, DataBindingEntity entity)
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
{
LayerProperty = layerProperty;
Entity = entity;
ApplyToDataBinding();
// Load will add children so be initialized before that
Initialize();
Load();
}
/// <summary>
/// Gets the layer property this data binding targets
/// </summary>
public BaseLayerProperty LayerProperty { get; }
/// <summary>
/// Gets the data binding registration this data binding is based upon
/// </summary>
public DataBindingRegistration Registration { get; private set; }
public DataBindingRegistration<TLayerProperty, TProperty> Registration { get; private set; }
/// <summary>
/// Gets the layer property this data binding targets
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary>
/// Gets the converter used to apply this data binding to the <see cref="LayerProperty" />
/// </summary>
public DataBindingConverter Converter { get; private set; }
/// <summary>
/// Gets the property on the <see cref="LayerProperty" /> this data binding targets
/// </summary>
public PropertyInfo TargetProperty { get; private set; }
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; private set; }
/// <summary>
/// Gets the currently used instance of the data model that contains the source of the data binding
@ -82,7 +77,7 @@ namespace Artemis.Core
/// <summary>
/// Gets a list of modifiers applied to this data binding
/// </summary>
public IReadOnlyList<DataBindingModifier> Modifiers => _modifiers.AsReadOnly();
public IReadOnlyList<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly();
/// <summary>
/// Gets the compiled function that gets the current value of the data binding target
@ -91,11 +86,57 @@ namespace Artemis.Core
internal DataBindingEntity Entity { get; }
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="deltaTime">The time in seconds that passed since the last update</param>
public void Update(double deltaTime)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// Data bindings cannot go back in time like brushes
deltaTime = Math.Max(0, deltaTime);
_easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime));
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
}
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
return;
var converterValue = Converter.GetValue();
var value = GetValue(converterValue);
Converter.ApplyValue(value);
}
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Dispose();
}
/// <summary>
/// Adds a modifier to the data binding's <see cref="Modifiers" /> collection
/// </summary>
public void AddModifier(DataBindingModifier modifier)
public void AddModifier(DataBindingModifier<TLayerProperty, TProperty> modifier)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (!_modifiers.Contains(modifier))
{
modifier.DataBinding = this;
@ -108,8 +149,11 @@ namespace Artemis.Core
/// <summary>
/// Removes a modifier from the data binding's <see cref="Modifiers" /> collection
/// </summary>
public void RemoveModifier(DataBindingModifier modifier)
public void RemoveModifier(DataBindingModifier<TLayerProperty, TProperty> modifier)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (_modifiers.Contains(modifier))
{
modifier.DataBinding = null;
@ -126,6 +170,9 @@ namespace Artemis.Core
/// <param name="path">The path pointing to the source inside the data model</param>
public void UpdateSource(DataModel dataModel, string path)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null)
@ -147,8 +194,11 @@ namespace Artemis.Core
/// </summary>
/// <param name="baseValue">The base value of the property the data binding is applied to</param>
/// <returns></returns>
public object GetValue(object baseValue)
public TProperty GetValue(TProperty baseValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (CompiledTargetAccessor == null || Converter == null)
return baseValue;
@ -156,8 +206,8 @@ namespace Artemis.Core
foreach (var dataBindingModifier in Modifiers)
dataBindingValue = dataBindingModifier.Apply(dataBindingValue);
var value = Convert.ChangeType(dataBindingValue, TargetProperty.PropertyType);
TProperty value = Converter.ConvertFromObject(dataBindingValue);
// If no easing is to be applied simple return whatever the current value is
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
return value;
@ -170,19 +220,12 @@ namespace Artemis.Core
return GetInterpolatedValue();
}
private void ResetEasing(object value)
{
_previousValue = GetInterpolatedValue();
_currentValue = value;
_easingProgress = TimeSpan.Zero;
}
/// <summary>
/// Returns the type of the target property of this data binding
/// </summary>
public Type GetTargetType()
{
return TargetProperty.PropertyType;
return Registration.PropertyExpression.ReturnType;
}
/// <summary>
@ -193,105 +236,47 @@ namespace Artemis.Core
return SourceDataModel?.GetTypeAtPath(SourcePropertyPath);
}
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="deltaTime">The time in seconds that passed since the last update</param>
public void Update(double deltaTime)
private void Initialize()
{
// Data bindings cannot go back in time like brushes
deltaTime = Math.Max(0, deltaTime);
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
_easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime));
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
}
/// <summary>
/// Updates the value on the <see cref="LayerProperty" /> according to the data binding
/// </summary>
public void ApplyToProperty()
{
if (Converter == null)
return;
var value = GetValue(Converter.GetValue());
Converter.ApplyValue(GetValue(value));
}
internal void ApplyToEntity()
{
// General
Entity.TargetProperty = TargetProperty?.Name;
Entity.DataBindingMode = (int) Mode;
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
// Data model
Entity.SourceDataModelGuid = SourceDataModel?.PluginInfo?.Guid;
Entity.SourcePropertyPath = SourcePropertyPath;
// Modifiers
Entity.Modifiers.Clear();
foreach (var dataBindingModifier in Modifiers)
{
dataBindingModifier.ApplyToEntity();
Entity.Modifiers.Add(dataBindingModifier.Entity);
}
}
internal void ApplyToDataBinding()
{
// General
ApplyRegistration(LayerProperty.DataBindingRegistrations.FirstOrDefault(p => p.Property.Name == Entity.TargetProperty));
Mode = (DataBindingMode) Entity.DataBindingMode;
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
// Data model is done during Initialize
// Modifiers
foreach (var dataBindingModifierEntity in Entity.Modifiers)
_modifiers.Add(new DataBindingModifier(this, dataBindingModifierEntity));
}
internal void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService)
{
// Source
if (Entity.SourceDataModelGuid != null && SourceDataModel == null)
{
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.SourceDataModelGuid.Value);
var dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath))
UpdateSource(dataModel, Entity.SourcePropertyPath);
}
// Modifiers
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Initialize(dataModelService, dataBindingService);
}
private void ApplyRegistration(DataBindingRegistration dataBindingRegistration)
private void ResetEasing(TProperty value)
{
_previousValue = GetInterpolatedValue();
_currentValue = value;
_easingProgress = TimeSpan.Zero;
}
private void ApplyRegistration(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
{
if (dataBindingRegistration != null)
dataBindingRegistration.DataBinding = this;
Converter = dataBindingRegistration?.Converter;
Registration = dataBindingRegistration;
TargetProperty = dataBindingRegistration?.Property;
if (GetTargetType().IsValueType)
{
if (_currentValue == null)
_currentValue = GetTargetType().GetDefault();
_currentValue = default;
if (_previousValue == null)
_previousValue = _currentValue;
_previousValue = default;
}
Converter?.Initialize(this);
}
private object GetInterpolatedValue()
private TProperty GetInterpolatedValue()
{
if (_easingProgress == EasingTime || !Converter.SupportsInterpolate)
return _currentValue;
@ -318,6 +303,77 @@ namespace Artemis.Core
CompiledTargetAccessor = lambda.Compile();
}
#region Storage
/// <inheritdoc />
public void Load()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// General
var registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression);
ApplyRegistration(registration);
Mode = (DataBindingMode) Entity.DataBindingMode;
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
// Data model is done during Initialize
// Modifiers
foreach (var dataBindingModifierEntity in Entity.Modifiers)
_modifiers.Add(new DataBindingModifier<TLayerProperty, TProperty>(this, dataBindingModifierEntity));
}
/// <inheritdoc />
public void Save()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity))
LayerProperty.Entity.DataBindingEntities.Add(Entity);
// General
Entity.TargetExpression = Registration.PropertyExpression.ToString();
Entity.DataBindingMode = (int) Mode;
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
// Data model
if (SourceDataModel != null)
{
Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid;
Entity.SourcePropertyPath = SourcePropertyPath;
}
// Modifiers
Entity.Modifiers.Clear();
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Save();
}
#endregion
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.SourceDataModelGuid && dataModel.ContainsPath(Entity.SourcePropertyPath))
UpdateSource(dataModel, Entity.SourcePropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (SourceDataModel != e.Registration.DataModel)
return;
SourceDataModel = null;
CompiledTargetAccessor = null;
}
#endregion
#region Events
/// <summary>

View File

@ -1,29 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core
{
/// <summary>
/// A data binding converter that acts as the bridge between a <see cref="DataBinding" /> and a
/// <see cref="LayerProperty{T}" />
/// Represents a data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
/// </summary>
public abstract class DataBindingConverter
public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter
{
internal Func<object> ValueGetter { get; set; }
internal Action<object> ValueSetter { get; set; }
/// <summary>
/// A dynamically compiled getter pointing to the data bound property
/// </summary>
public Func<TLayerProperty, TProperty> GetExpression { get; private set; }
/// <summary>
/// A dynamically compiled setter pointing to the data bound property
/// </summary>
public Action<TProperty> ValueTypeSetExpression { get; private set; }
public Action<TLayerProperty, TProperty> ReferenceTypeSetExpression { get; private set; }
/// <summary>
/// Gets the data binding this converter is applied to
/// </summary>
public DataBinding DataBinding { get; private set; }
/// <summary>
/// Gets the type this converter supports
/// </summary>
public Type SupportedType { get; protected set; }
public DataBinding<TLayerProperty, TProperty> DataBinding { get; private set; }
/// <summary>
/// Gets whether or not this data binding converter supports the <see cref="Sum" /> method
@ -35,17 +37,13 @@ namespace Artemis.Core
/// </summary>
public bool SupportsInterpolate { get; protected set; }
/// <summary>
/// Called when the data binding converter has been initialized and the <see cref="DataBinding" /> is available
/// </summary>
protected virtual void OnInitialized()
{
}
/// <inheritdoc />
public Type SupportedType => typeof(TProperty);
/// <summary>
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
/// </summary>
public abstract object Sum(object a, object b);
public abstract TProperty Sum(TProperty a, TProperty b);
/// <summary>
/// Returns the the interpolated value between <paramref name="a" /> and <paramref name="b" /> on a scale (generally)
@ -56,66 +54,117 @@ namespace Artemis.Core
/// <param name="b">The value to interpolate towards</param>
/// <param name="progress">The progress of the interpolation between 0.0 and 1.0</param>
/// <returns></returns>
public abstract object Interpolate(object a, object b, double progress);
public abstract TProperty Interpolate(TProperty a, TProperty b, double progress);
/// <summary>
/// Applies the <paramref name="value" /> to the layer property
/// </summary>
/// <param name="value"></param>
public abstract void ApplyValue(object value);
public virtual void ApplyValue(TProperty value)
{
if (ReferenceTypeSetExpression != null)
ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value);
else if (ValueTypeSetExpression != null)
ValueTypeSetExpression(value);
}
/// <summary>
/// Returns the current base value of the data binding
/// </summary>
public abstract object GetValue();
public virtual TProperty GetValue()
{
return GetExpression(DataBinding.LayerProperty.CurrentValue);
}
internal void Initialize(DataBinding dataBinding)
/// <summary>
/// Converts the provided object to a type of <typeparamref name="TProperty" />
/// </summary>
public virtual TProperty ConvertFromObject(object source)
{
return (TProperty) Convert.ChangeType(source, typeof(TProperty));
}
/// <summary>
/// Called when the data binding converter has been initialized and the <see cref="DataBinding" /> is available
/// </summary>
protected virtual void OnInitialized()
{
}
internal void Initialize(DataBinding<TLayerProperty, TProperty> dataBinding)
{
DataBinding = dataBinding;
ValueGetter = CreateValueGetter();
ValueSetter = CreateValueSetter();
GetExpression = dataBinding.Registration.PropertyExpression.Compile();
CreateSetExpression();
OnInitialized();
}
private Func<object> CreateValueGetter()
private void CreateSetExpression()
{
if (DataBinding.TargetProperty?.DeclaringType == null)
return null;
// If the registration does not point towards a member of LayerProperty<T>.CurrentValue, assign directly to LayerProperty<T>.CurrentValue
if (DataBinding.Registration.Member == null)
{
CreateSetCurrentValueExpression();
return;
}
var getterMethod = DataBinding.TargetProperty.GetGetMethod();
if (getterMethod == null)
return null;
var constant = Expression.Constant(DataBinding.LayerProperty);
// The path is null if the registration is applied to the root (LayerProperty.CurrentValue)
var property = DataBinding.Registration.Path == null
? Expression.Property(constant, DataBinding.TargetProperty)
: (MemberExpression) DataBinding.Registration.Path.Split('.').Aggregate<string, Expression>(constant, Expression.Property);
// Ensure the member of LayerProperty<T>.CurrentValue has a setter
MethodInfo setterMethod = null;
if (DataBinding.Registration.Member is PropertyInfo propertyInfo)
setterMethod = propertyInfo.GetSetMethod();
// If there is no setter, the built-in data binding cannot do its job, stay null
if (setterMethod == null)
return;
// The get method should cast to the object since it receives whatever type the property is
var body = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<object>>(body);
return lambda.Compile();
// If LayerProperty<T>.CurrentValue is a value type, assign it directly to LayerProperty<T>.CurrentValue after applying the changes
if (typeof(TLayerProperty).IsValueType)
CreateSetValueTypeExpression();
// If it is a reference type it can safely be updated by its reference
else
CreateSetReferenceTypeExpression();
}
private Action<object> CreateValueSetter()
private void CreateSetReferenceTypeExpression()
{
if (DataBinding.TargetProperty?.DeclaringType == null)
return null;
var propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
var parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue");
var memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member);
var assignment = Expression.Assign(memberAccess, propertyValue);
var referenceTypeLambda = Expression.Lambda<Action<TLayerProperty, TProperty>>(assignment, parameter, propertyValue);
var setterMethod = DataBinding.TargetProperty.GetSetMethod();
if (setterMethod == null)
return null;
ReferenceTypeSetExpression = referenceTypeLambda.Compile();
}
var constant = Expression.Constant(DataBinding.LayerProperty);
var propertyValue = Expression.Parameter(typeof(object), "propertyValue");
private void CreateSetValueTypeExpression()
{
var propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
var variableCurrent = Expression.Variable(typeof(TLayerProperty), "current");
var layerProperty = Expression.Constant(DataBinding.LayerProperty);
var layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty,
DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]);
// The assign method should cast to the proper type since it receives an object
var body = Expression.Call(constant, setterMethod, Expression.Convert(propertyValue, DataBinding.TargetProperty.PropertyType));
var lambda = Expression.Lambda<Action<object>>(body, propertyValue);
return lambda.Compile();
var body = Expression.Block(
new[] {variableCurrent},
Expression.Assign(variableCurrent, layerPropertyMemberAccess),
Expression.Assign(Expression.MakeMemberAccess(variableCurrent, DataBinding.Registration.Member), propertyValue),
Expression.Assign(layerPropertyMemberAccess, variableCurrent)
);
var valueTypeLambda = Expression.Lambda<Action<TProperty>>(body, propertyValue);
ValueTypeSetExpression = valueTypeLambda.Compile();
}
private void CreateSetCurrentValueExpression()
{
var propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
var layerProperty = Expression.Constant(DataBinding.LayerProperty);
var layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty,
DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]);
var body = Expression.Assign(layerPropertyMemberAccess, propertyValue);
var lambda = Expression.Lambda<Action<TProperty>>(body, propertyValue);
ValueTypeSetExpression = lambda.Compile();
}
}
}

View File

@ -2,43 +2,43 @@
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.DataBindings;
using Newtonsoft.Json;
namespace Artemis.Core
{
/// <summary>
/// Modifies a data model value in a way defined by the modifier type
/// </summary>
public class DataBindingModifier
/// <inheritdoc />
public class DataBindingModifier<TLayerProperty, TProperty> : IDataBindingModifier
{
private DataBinding _dataBinding;
private DataBinding<TLayerProperty, TProperty> _dataBinding;
private bool _disposed;
/// <summary>
/// Creates a new instance of the <see cref="DataBindingModifier" /> class
/// Creates a new instance of the <see cref="DataBindingModifier{TLayerProperty,TProperty}" /> class
/// </summary>
/// <param name="dataBinding">The data binding the modifier is to be applied to</param>
/// <param name="parameterType">The type of the parameter, can either be dynamic (based on a data model value) or static</param>
public DataBindingModifier(ProfileRightSideType parameterType)
public DataBindingModifier(DataBinding<TLayerProperty, TProperty> dataBinding, ProfileRightSideType parameterType)
{
_dataBinding = dataBinding ?? throw new ArgumentNullException(nameof(dataBinding));
ParameterType = parameterType;
Entity = new DataBindingModifierEntity();
ApplyToEntity();
Initialize();
Save();
}
internal DataBindingModifier(DataBinding dataBinding, DataBindingModifierEntity entity)
internal DataBindingModifier(DataBinding<TLayerProperty, TProperty> dataBinding, DataBindingModifierEntity entity)
{
DataBinding = dataBinding;
_dataBinding = dataBinding;
Entity = entity;
ApplyToDataBindingModifier();
Load();
Initialize();
}
/// <summary>
/// Gets the data binding this modifier is applied to
/// </summary>
public DataBinding DataBinding
public DataBinding<TLayerProperty, TProperty> DataBinding
{
get => _dataBinding;
internal set
@ -86,6 +86,52 @@ namespace Artemis.Core
internal DataBindingModifierEntity Entity { get; set; }
/// <inheritdoc />
public void Save()
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (!DataBinding.Entity.Modifiers.Contains(Entity))
DataBinding.Entity.Modifiers.Add(Entity);
// Modifier
if (ModifierType != null)
{
Entity.ModifierType = ModifierType.GetType().Name;
Entity.ModifierTypePluginGuid = ModifierType.PluginInfo.Guid;
}
// General
Entity.Order = Order;
Entity.ParameterType = (int) ParameterType;
// Parameter
if (ParameterDataModel != null)
{
Entity.ParameterDataModelGuid = ParameterDataModel.PluginInfo.Guid;
Entity.ParameterPropertyPath = ParameterPropertyPath;
}
Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue);
}
/// <inheritdoc />
public void Load()
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
// Modifier type is done during Initialize
// General
Order = Entity.Order;
ParameterType = (ProfileRightSideType) Entity.ParameterType;
// Parameter is done during initialize
}
/// <summary>
/// Applies the modifier to the provided value
/// </summary>
@ -93,6 +139,9 @@ namespace Artemis.Core
/// <returns>The modified value</returns>
public object Apply(object currentValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (ModifierType == null)
return currentValue;
@ -117,6 +166,9 @@ namespace Artemis.Core
/// <param name="modifierType"></param>
public void UpdateModifierType(DataBindingModifierType modifierType)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
// Calling CreateExpression will clear compiled expressions
if (modifierType == null)
{
@ -125,7 +177,7 @@ namespace Artemis.Core
return;
}
var targetType = DataBinding.TargetProperty.PropertyType;
var targetType = DataBinding.GetTargetType();
if (!modifierType.SupportsType(targetType))
{
throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " +
@ -143,6 +195,9 @@ namespace Artemis.Core
/// <param name="path">The path pointing to the parameter inside the data model</param>
public void UpdateParameter(DataModel dataModel, string path)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null)
@ -167,11 +222,14 @@ namespace Artemis.Core
/// <param name="staticValue">The static value to use as a parameter</param>
public void UpdateParameter(object staticValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
ParameterType = ProfileRightSideType.Static;
ParameterDataModel = null;
ParameterPropertyPath = null;
var targetType = DataBinding.TargetProperty.PropertyType;
var targetType = DataBinding.GetTargetType();
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == targetType)
@ -187,12 +245,17 @@ namespace Artemis.Core
CreateExpression();
}
internal void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService)
private void Initialize()
{
DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded;
DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved;
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
// Modifier type
if (Entity.ModifierTypePluginGuid != null && ModifierType == null)
{
var modifierType = dataBindingService.GetModifierType(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType);
var modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType;
if (modifierType != null)
UpdateModifierType(modifierType);
}
@ -200,7 +263,7 @@ namespace Artemis.Core
// Dynamic parameter
if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterDataModelGuid != null && ParameterDataModel == null)
{
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ParameterDataModelGuid.Value);
var dataModel = DataModelStore.Get(Entity.ParameterDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.ParameterPropertyPath))
UpdateParameter(dataModel, Entity.ParameterPropertyPath);
}
@ -208,7 +271,7 @@ namespace Artemis.Core
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null)
{
// Use the target type so JSON.NET has a better idea what to do
var targetType = DataBinding.TargetProperty.PropertyType;
var targetType = DataBinding.GetTargetType();
object staticValue;
try
@ -218,7 +281,7 @@ namespace Artemis.Core
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
dataBindingService.LogModifierDeserializationFailure(this, e);
DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e);
staticValue = Activator.CreateInstance(targetType);
}
@ -226,34 +289,6 @@ namespace Artemis.Core
}
}
internal void ApplyToEntity()
{
// Modifier
Entity.ModifierType = ModifierType?.GetType().Name;
Entity.ModifierTypePluginGuid = ModifierType?.PluginInfo.Guid;
// General
Entity.Order = Order;
Entity.ParameterType = (int) ParameterType;
// Parameter
Entity.ParameterDataModelGuid = ParameterDataModel?.PluginInfo.Guid;
Entity.ParameterPropertyPath = ParameterPropertyPath;
Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue);
}
internal void ApplyToDataBindingModifier()
{
// Modifier type is done during Initialize
// General
Order = Entity.Order;
ParameterType = (ProfileRightSideType) Entity.ParameterType;
// Parameter is done during initialize
}
private void CreateExpression()
{
CompiledParameterAccessor = null;
@ -285,5 +320,51 @@ namespace Artemis.Core
Expression.Property
);
}
#region Event handlers
private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object sender, DataBindingModifierTypeStoreEvent e)
{
if (ModifierType != null)
return;
var modifierType = e.TypeRegistration.DataBindingModifierType;
if (modifierType.PluginInfo.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType)
UpdateModifierType(modifierType);
}
private void DataBindingModifierTypeStoreOnDataBindingModifierRemoved(object sender, DataBindingModifierTypeStoreEvent e)
{
if (e.TypeRegistration.DataBindingModifierType == ModifierType)
UpdateModifierType(null);
}
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.ParameterDataModelGuid && dataModel.ContainsPath(Entity.ParameterPropertyPath))
UpdateParameter(dataModel, Entity.ParameterPropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (e.Registration.DataModel != ParameterDataModel)
return;
ParameterDataModel = null;
CompiledParameterAccessor = null;
}
#endregion
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded;
DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
}
}
}

View File

@ -1,22 +1,63 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core
{
public class DataBindingRegistration
/// <inheritdoc />
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
{
internal DataBindingRegistration(BaseLayerProperty layerProperty, PropertyInfo property, DataBindingConverter converter, string path)
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty,
DataBindingConverter<TLayerProperty, TProperty> converter,
Expression<Func<TLayerProperty, TProperty>> propertyExpression)
{
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
Property = property ?? throw new ArgumentNullException(nameof(property));
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
Path = path;
PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression));
if (propertyExpression.Body is MemberExpression memberExpression)
Member = memberExpression.Member;
}
public DataBinding DataBinding { get; internal set; }
public BaseLayerProperty LayerProperty { get; }
public PropertyInfo Property { get; }
public DataBindingConverter Converter { get; }
public string Path { get; }
/// <summary>
/// Gets the layer property this registration was made on
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary>
/// Gets the converter that's used by the data binding
/// </summary>
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
/// <summary>
/// Gets the expression that that accesses the property
/// </summary>
public Expression<Func<TLayerProperty, TProperty>> PropertyExpression { get; }
/// <summary>
/// Gets the member the <see cref="PropertyExpression" /> targets
/// <para><c>null</c> if the <see cref="PropertyExpression" /> is not a member expression</para>
/// </summary>
public MemberInfo Member { get; }
/// <summary>
/// Gets the data binding created using this registration
/// </summary>
public DataBinding<TLayerProperty, TProperty> DataBinding { get; internal set; }
/// <inheritdoc />
public IDataBinding CreateDataBinding()
{
if (DataBinding != null)
return DataBinding;
var dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString());
if (dataBinding == null)
return null;
DataBinding = new DataBinding<TLayerProperty, TProperty>(LayerProperty, dataBinding);
return DataBinding;
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
/// <see cref="DataModel" />
/// </summary>
public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable
{
/// <summary>
/// Applies the data binding to the layer property
/// </summary>
void Apply();
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
/// </summary>
public interface IDataBindingConverter
{
/// <summary>
/// Gets the type this converter supports
/// </summary>
public Type SupportedType { get; }
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Modifies a data model value in a way defined by the modifier type
/// </summary>
public interface IDataBindingModifier : IStorageModel, IDisposable
{
}
}

View File

@ -0,0 +1,14 @@
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding registration
/// </summary>
public interface IDataBindingRegistration
{
/// <summary>
/// If found, creates a data binding from storage
/// </summary>
/// <returns></returns>
IDataBinding CreateDataBinding();
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Services;
namespace Artemis.Core
{
@ -10,9 +9,6 @@ namespace Artemis.Core
/// </summary>
public abstract class DataBindingModifierType
{
private IDataBindingService _dataBindingService;
private bool _registered;
/// <summary>
/// Gets the plugin info this data binding modifier belongs to
/// <para>Note: Not set until after registering</para>
@ -35,10 +31,16 @@ namespace Artemis.Core
public abstract string Icon { get; }
/// <summary>
/// Gets or sets whether this modifier supports a parameter, defaults to true
/// Gets or sets whether this modifier supports a parameter, defaults to <c>true</c>
/// </summary>
public bool SupportsParameter { get; protected set; } = true;
/// <summary>
/// Gets or sets the preferred parameter type
/// <para>If <c>null</c>, the parameter type will match the source property</para>
/// </summary>
public Type PreferredParameterType { get; protected set; } = null;
/// <summary>
/// Returns whether the given type is supported by the modifier
/// </summary>
@ -63,35 +65,5 @@ namespace Artemis.Core
/// </param>
/// <returns>The modified value, must be a value of a type contained in <see cref="CompatibleTypes" /></returns>
public abstract object Apply(object currentValue, object parameterValue);
internal void Register(PluginInfo pluginInfo, IDataBindingService dataBindingService)
{
if (_registered)
return;
PluginInfo = pluginInfo;
_dataBindingService = dataBindingService;
if (PluginInfo != Constants.CorePluginInfo)
PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled;
_registered = true;
}
internal void Unsubscribe()
{
if (!_registered)
return;
if (PluginInfo != Constants.CorePluginInfo)
PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled;
_registered = false;
}
private void InstanceOnPluginDisabled(object sender, EventArgs e)
{
// The service will call Unsubscribe
_dataBindingService.RemoveModifierType(this);
}
}
}

View File

@ -5,6 +5,11 @@ namespace Artemis.Core
{
internal class DivideModifierType : DataBindingModifierType
{
public DivideModifierType()
{
PreferredParameterType = typeof(float);
}
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
public override string Description => "Divide by";

View File

@ -5,6 +5,11 @@ namespace Artemis.Core
{
internal class MultiplicationModifierType : DataBindingModifierType
{
public MultiplicationModifierType()
{
PreferredParameterType = typeof(float);
}
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
public override string Description => "Multiply by";

View File

@ -8,24 +8,35 @@ using SkiaSharp;
namespace Artemis.Core
{
/// <summary>
/// Represents a folder in a <see cref="Profile" />
/// </summary>
public sealed class Folder : RenderProfileElement
{
private SKBitmap _folderBitmap;
public Folder(Profile profile, ProfileElement parent, string name)
/// <summary>
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
/// <paramref name="parent" />
/// </summary>
/// <param name="parent">The parent of the folder</param>
/// <param name="name">The name of the folder</param>
public Folder(ProfileElement parent, string name)
{
FolderEntity = new FolderEntity();
EntityId = Guid.NewGuid();
Profile = profile;
Parent = parent;
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
Profile = Parent.Profile;
Name = name;
Enabled = true;
DisplayContinuously = true;
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
ApplyRenderElementDefaults();
Parent.AddChild(this);
}
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
@ -42,21 +53,7 @@ namespace Artemis.Core
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
_expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups);
// Load child folders
foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(profile, this, childFolder));
// Load child layers
foreach (var childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (var index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
ApplyRenderElementEntity();
Load();
}
internal FolderEntity FolderEntity { get; set; }
@ -197,21 +194,6 @@ namespace Artemis.Core
canvas.Restore();
}
/// <summary>
/// Adds a new folder to the bottom of this folder
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public Folder AddFolder(string name)
{
if (_disposed)
throw new ObjectDisposedException("Folder");
var folder = new Folder(Profile, this, name) {Order = Children.LastOrDefault()?.Order ?? 1};
AddChild(folder);
return folder;
}
/// <inheritdoc />
public override void AddChild(ProfileElement child, int? order = null)
{
@ -260,26 +242,35 @@ namespace Artemis.Core
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
foreach (var baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
_layerEffects.Clear();
_disposed = true;
foreach (var profileElement in Children)
profileElement.Dispose();
ChildrenList.Clear();
_folderBitmap?.Dispose();
_folderBitmap = null;
Profile = null;
_disposed = true;
base.Dispose(disposing);
}
internal override void Load()
{
_expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
internal override void ApplyToEntity()
// Load child folders
foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(Profile, this, childFolder));
// Load child layers
foreach (var childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (var index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
LoadRenderElement();
}
internal override void Save()
{
if (_disposed)
throw new ObjectDisposedException("Folder");
@ -295,11 +286,7 @@ namespace Artemis.Core
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
ApplyRenderElementToEntity();
// Conditions
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
DisplayConditionGroup?.ApplyToEntity();
SaveRenderElement();
}
#region Events

View File

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using SkiaSharp;
@ -12,8 +12,7 @@ using SkiaSharp;
namespace Artemis.Core
{
/// <summary>
/// Represents a layer on a profile. To create new layers use the <see cref="RenderElementService" /> by injecting
/// <see cref="IRenderElementService" /> into your code
/// Represents a layer in a <see cref="Profile" />
/// </summary>
public sealed class Layer : RenderProfileElement
{
@ -24,47 +23,49 @@ namespace Artemis.Core
private List<ArtemisLed> _leds;
private LayerTransformProperties _transform;
internal Layer(Profile profile, ProfileElement parent, string name)
/// <summary>
/// Creates a new instance of the <see cref="Layer" /> class and adds itself to the child collection of the provided
/// <paramref name="parent" />
/// </summary>
/// <param name="parent">The parent of the layer</param>
/// <param name="name">The name of the layer</param>
public Layer(ProfileElement parent, string name)
{
LayerEntity = new LayerEntity();
EntityId = Guid.NewGuid();
Profile = profile;
Parent = parent;
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
Profile = Parent.Profile;
Name = name;
Enabled = true;
DisplayContinuously = true;
General = new LayerGeneralProperties {IsCorePropertyGroup = true};
Transform = new LayerTransformProperties {IsCorePropertyGroup = true};
General = new LayerGeneralProperties();
Transform = new LayerTransformProperties();
_layerEffects = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>();
_expandedPropertyGroups = new List<string>();
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
Initialize();
ApplyRenderElementDefaults();
Parent.AddChild(this);
}
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
{
LayerEntity = layerEntity;
EntityId = layerEntity.Id;
Profile = profile;
Parent = parent;
Name = layerEntity.Name;
Enabled = layerEntity.Enabled;
Order = layerEntity.Order;
General = new LayerGeneralProperties {IsCorePropertyGroup = true};
Transform = new LayerTransformProperties {IsCorePropertyGroup = true};
General = new LayerGeneralProperties();
Transform = new LayerTransformProperties();
_layerEffects = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>();
_expandedPropertyGroups = new List<string>();
_expandedPropertyGroups.AddRange(layerEntity.ExpandedPropertyGroups);
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
ApplyRenderElementEntity();
Load();
Initialize();
}
internal LayerEntity LayerEntity { get; set; }
@ -118,56 +119,63 @@ namespace Artemis.Core
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
/// <inheritdoc />
public override List<BaseLayerPropertyKeyframe> GetAllKeyframes()
{
if (_disposed)
throw new ObjectDisposedException("Layer");
var keyframes = base.GetAllKeyframes();
foreach (var baseLayerProperty in General.GetAllLayerProperties())
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
foreach (var baseLayerProperty in Transform.GetAllLayerProperties())
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
if (LayerBrush?.BaseProperties != null)
{
foreach (var baseLayerProperty in LayerBrush.BaseProperties.GetAllLayerProperties())
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
}
return keyframes;
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
_disposed = true;
// Brush first in case it depends on any of the other disposables during it's own disposal
_layerBrush?.Dispose();
_layerBrush = null;
foreach (var baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
_layerEffects.Clear();
_general?.Dispose();
_general = null;
_layerBitmap?.Dispose();
_layerBitmap = null;
_transform?.Dispose();
_transform = null;
Profile = null;
base.Dispose(disposing);
}
#endregion
private void Initialize()
{
LayerBrushStore.LayerBrushAdded += LayerBrushStoreOnLayerBrushAdded;
LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved;
// Layers have two hardcoded property groups, instantiate them
var generalAttribute = Attribute.GetCustomAttribute(
GetType().GetProperty(nameof(General)),
typeof(PropertyGroupDescriptionAttribute)
);
var transformAttribute = Attribute.GetCustomAttribute(
GetType().GetProperty(nameof(Transform)),
typeof(PropertyGroupDescriptionAttribute)
);
General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute;
General.Initialize(this, "General.", Constants.CorePluginInfo);
Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute;
Transform.Initialize(this, "Transform.", Constants.CorePluginInfo);
General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged;
ApplyShapeType();
ActivateLayerBrush();
}
#region Storage
internal override void ApplyToEntity()
internal override void Load()
{
EntityId = LayerEntity.Id;
Name = LayerEntity.Name;
Enabled = LayerEntity.Enabled;
Order = LayerEntity.Order;
_expandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
LoadRenderElement();
}
internal override void Save()
{
if (_disposed)
throw new ObjectDisposedException("Layer");
@ -198,24 +206,13 @@ namespace Artemis.Core
LayerEntity.Leds.Add(ledEntity);
}
// Conditions
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
DisplayConditionGroup?.ApplyToEntity();
ApplyRenderElementToEntity();
SaveRenderElement();
}
#endregion
#region Shape management
private void GeneralOnPropertyGroupInitialized(object sender, EventArgs e)
{
ApplyShapeType();
General.ShapeType.BaseValueChanged -= ShapeTypeOnBaseValueChanged;
General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged;
}
private void ShapeTypeOnBaseValueChanged(object sender, EventArgs e)
{
ApplyShapeType();
@ -312,18 +309,6 @@ namespace Artemis.Core
}
}
/// <inheritdoc />
public override List<BaseLayerProperty> GetAllLayerProperties()
{
var result = base.GetAllLayerProperties();
result.AddRange(General.GetAllLayerProperties());
result.AddRange(Transform.GetAllLayerProperties());
if (LayerBrush?.BaseProperties != null)
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
return result;
}
/// <inheritdoc />
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
{
@ -383,7 +368,6 @@ namespace Artemis.Core
if (Parent is Folder parentFolder)
targetLocation = Path.Bounds.Location - parentFolder.Path.Bounds.Location;
canvas.DrawBitmap(_layerBitmap, targetLocation, layerPaint);
}
@ -680,7 +664,55 @@ namespace Artemis.Core
#endregion
#region Activation
#region Brush management
/// <summary>
/// Changes the current layer brush to the brush described in the provided <paramref name="descriptor" />
/// </summary>
public void ChangeLayerBrush(LayerBrushDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
if (LayerBrush != null)
{
var brush = LayerBrush;
LayerBrush = null;
brush.Dispose();
}
// Ensure the brush reference matches the brush
var current = General.BrushReference.BaseValue;
if (!descriptor.MatchesLayerBrushReference(current))
General.BrushReference.BaseValue = new LayerBrushReference(descriptor);
ActivateLayerBrush();
}
/// <summary>
/// Removes the current layer brush from the layer
/// </summary>
public void RemoveLayerBrush()
{
if (LayerBrush == null)
return;
var brush = LayerBrush;
DeactivateLayerBrush();
LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush."));
}
internal void ActivateLayerBrush()
{
var current = General.BrushReference.CurrentValue;
if (current == null)
return;
var descriptor = LayerBrushStore.Get(current.BrushPluginGuid, current.BrushType)?.LayerBrushDescriptor;
descriptor?.CreateInstance(this);
OnLayerBrushUpdated();
}
internal void DeactivateLayerBrush()
{
@ -690,16 +722,29 @@ namespace Artemis.Core
var brush = LayerBrush;
LayerBrush = null;
brush.Dispose();
OnLayerBrushUpdated();
}
internal void RemoveLayerBrush()
#endregion
#region Event handlers
private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e)
{
if (LayerBrush == null)
if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor)
DeactivateLayerBrush();
}
private void LayerBrushStoreOnLayerBrushAdded(object sender, LayerBrushStoreEvent e)
{
if (LayerBrush != null)
return;
var brush = LayerBrush;
DeactivateLayerBrush();
LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush."));
var current = General.BrushReference.CurrentValue;
if (e.Registration.Plugin.PluginInfo.Guid == current.BrushPluginGuid &&
e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
ActivateLayerBrush();
}
#endregion

View File

@ -17,5 +17,15 @@ namespace Artemis.Core
/// The full type name of the brush descriptor
/// </summary>
public string BrushType { get; set; }
public LayerBrushReference()
{
}
public LayerBrushReference(LayerBrushDescriptor descriptor)
{
BrushPluginGuid = descriptor.LayerBrushProvider.PluginInfo.Guid;
BrushType = descriptor.LayerBrushType.Name;
}
}
}

View File

@ -1,273 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core
{
/// <summary>
/// For internal use only, to implement your own layer property type, extend <see cref="LayerProperty{T}" /> instead.
/// </summary>
public abstract class BaseLayerProperty
{
protected readonly List<DataBindingRegistration> _dataBindingRegistrations = new List<DataBindingRegistration>();
protected readonly List<DataBinding> _dataBindings = new List<DataBinding>();
private object _baseValue;
private object _currentValue;
private object _defaultValue;
private bool _isHidden;
private bool _keyframesEnabled;
internal BaseLayerProperty()
{
}
/// <summary>
/// Gets or sets the base value of this layer property without any keyframes applied
/// </summary>
public object BaseValue
{
get => _baseValue;
set
{
if (value != null && value.GetType() != GetPropertyType())
throw new ArtemisCoreException("Cannot update base value because of a type mismatch");
_baseValue = value;
}
}
/// <summary>
/// Gets the current value of this property as it is affected by it's keyframes, updated once every frame
/// </summary>
public object CurrentValue
{
get => _currentValue;
set
{
if (value != null && value.GetType() != GetPropertyType())
throw new ArtemisCoreException("Cannot update current value because of a type mismatch");
_currentValue = value;
}
}
/// <summary>
/// Gets or sets the default value of this layer property. If set, this value is automatically applied if the property
/// has no value in storage
/// </summary>
public object DefaultValue
{
get => _defaultValue;
set
{
if (value != null && value.GetType() != GetPropertyType())
throw new ArtemisCoreException("Cannot update default value because of a type mismatch");
_defaultValue = value;
}
}
/// <summary>
/// Gets a list containing the active data bindings
/// </summary>
public IReadOnlyList<DataBinding> DataBindings => _dataBindings.AsReadOnly();
/// <summary>
/// Gets a list containing all the data binding registrations
/// </summary>
public IReadOnlyList<DataBindingRegistration> DataBindingRegistrations => _dataBindingRegistrations.AsReadOnly();
/// <summary>
/// Gets the profile element (such as layer or folder) this effect is applied to
/// </summary>
public RenderProfileElement ProfileElement { get; internal set; }
/// <summary>
/// The parent group of this layer property, set after construction
/// </summary>
public LayerPropertyGroup Parent { get; internal set; }
/// <summary>
/// Gets whether keyframes are supported on this type of property
/// </summary>
public bool KeyframesSupported { get; protected internal set; } = true;
/// <summary>
/// Gets whether data bindings are supported on this type of property
/// </summary>
public bool DataBindingsSupported { get; protected internal set; } = true;
/// <summary>
/// Gets or sets whether keyframes are enabled on this property, has no effect if <see cref="KeyframesSupported" /> is
/// False
/// </summary>
public bool KeyframesEnabled
{
get => _keyframesEnabled;
set
{
if (_keyframesEnabled == value) return;
_keyframesEnabled = value;
OnKeyframesToggled();
}
}
/// <summary>
/// Gets or sets whether the property is hidden in the UI
/// </summary>
public bool IsHidden
{
get => _isHidden;
set
{
_isHidden = value;
OnVisibilityChanged();
}
}
/// <summary>
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
/// </summary>
public bool IsLoadedFromStorage { get; internal set; }
/// <summary>
/// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID
/// </summary>
public bool IsCoreProperty { get; internal set; }
/// <summary>
/// Gets the description attribute applied to this property
/// </summary>
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
/// <summary>
/// Gets a list of all the keyframes in their non-generic base form, without their values being available
/// </summary>
public abstract IReadOnlyList<BaseLayerPropertyKeyframe> BaseKeyframes { get; }
internal PropertyEntity PropertyEntity { get; set; }
internal LayerPropertyGroup LayerPropertyGroup { get; set; }
/// <summary>
/// Overrides the property value with the default value
/// </summary>
public abstract void ApplyDefaultValue();
/// <summary>
/// Returns the type of the property
/// </summary>
public abstract Type GetPropertyType();
/// <summary>
/// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values
/// </summary>
/// <param name="entity"></param>
/// <param name="layerPropertyGroup"></param>
/// <param name="fromStorage"></param>
internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage);
/// <summary>
/// Saves the property to the underlying property entity that was configured when calling
/// <see cref="ApplyToLayerProperty" />
/// </summary>
internal abstract void ApplyToEntity();
#region Data bindings
internal void InitializeDataBindings(IDataModelService dataModelService, IDataBindingService dataBindingService)
{
foreach (var dataBinding in DataBindings)
dataBinding.Initialize(dataModelService, dataBindingService);
}
/// <summary>
/// Adds a new data binding targeting the given property to the <see cref="DataBindings" /> collection
/// </summary>
/// <returns>The newly created data binding</returns>
public DataBinding EnableDataBinding(DataBindingRegistration dataBindingRegistration)
{
if (dataBindingRegistration.LayerProperty != this)
throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property");
var dataBinding = new DataBinding(dataBindingRegistration);
_dataBindings.Add(dataBinding);
return dataBinding;
}
/// <summary>
/// Removes the provided data binding from the <see cref="DataBindings" /> collection
/// </summary>
/// <param name="dataBinding">The data binding to remove</param>
public void DisableDataBinding(DataBinding dataBinding)
{
_dataBindings.Remove(dataBinding);
}
#endregion
#region Events
/// <summary>
/// Occurs once every frame when the layer property is updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs> Updated;
/// <summary>
/// Occurs when the base value of the layer property was updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs> BaseValueChanged;
/// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs> VisibilityChanged;
/// <summary>
/// Occurs when keyframes are enabled/disabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs> KeyframesToggled;
/// <summary>
/// Occurs when a new keyframe was added to the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs> KeyframeAdded;
/// <summary>
/// Occurs when a keyframe was removed from the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs> KeyframeRemoved;
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
protected virtual void OnBaseValueChanged()
{
BaseValueChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
}
#endregion
}
}

View File

@ -1,36 +0,0 @@
using System;
using Stylet;
namespace Artemis.Core
{
/// <summary>
/// For internal use only, use <see cref="LayerPropertyKeyframe{T}" /> instead.
/// </summary>
public abstract class BaseLayerPropertyKeyframe : PropertyChangedBase
{
internal BaseLayerPropertyKeyframe(BaseLayerProperty baseLayerProperty)
{
BaseLayerProperty = baseLayerProperty;
}
/// <summary>
/// The base class of the layer property this keyframe is applied to
/// </summary>
public BaseLayerProperty BaseLayerProperty { get; internal set; }
/// <summary>
/// The position of this keyframe in the timeline
/// </summary>
public abstract TimeSpan Position { get; set; }
/// <summary>
/// The easing function applied on the value of the keyframe
/// </summary>
public Easings.Functions EasingFunction { get; set; }
/// <summary>
/// Removes the keyframe from the layer property
/// </summary>
public abstract void Remove();
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core
{
/// <summary>
/// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI.
/// <para>
/// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will
/// initialize these for you.
/// </para>
/// </summary>
public interface ILayerProperty : IStorageModel, IUpdateModel, IDisposable
{
/// <summary>
/// Initializes the layer property
/// <para>
/// Note: This isn't done in the constructor to keep it parameterless which is easier for implementations of
/// <see cref="LayerProperty{T}" />
/// </para>
/// </summary>
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description);
/// <summary>
/// Returns a list off all data binding registrations
/// </summary>
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@ -17,52 +16,191 @@ namespace Artemis.Core
/// </para>
/// </summary>
/// <typeparam name="T">The type of property encapsulated in this layer property</typeparam>
public abstract class LayerProperty<T> : BaseLayerProperty
public abstract class LayerProperty<T> : ILayerProperty
{
private bool _isInitialized;
private List<LayerPropertyKeyframe<T>> _keyframes;
private bool _disposed;
/// <summary>
/// Creates a new instance of the <see cref="LayerProperty{T}" /> class
/// </summary>
protected LayerProperty()
{
_keyframes = new List<LayerPropertyKeyframe<T>>();
}
/// <summary>
/// Gets or sets the base value of this layer property without any keyframes applied
/// Gets the description attribute applied to this property
/// </summary>
public new T BaseValue
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
/// <summary>
/// Updates the property, applying keyframes and data bindings to the current value
/// </summary>
public void Update(double deltaTime)
{
get => base.BaseValue != null ? (T) base.BaseValue : default;
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
CurrentValue = BaseValue;
UpdateKeyframes();
UpdateDataBindings(deltaTime);
OnUpdated();
}
/// <summary>
/// Returns the type of the property
/// </summary>
public Type GetPropertyType()
{
return typeof(T);
}
#region Hierarchy
private bool _isHidden;
/// <summary>
/// Gets or sets whether the property is hidden in the UI
/// </summary>
public bool IsHidden
{
get => _isHidden;
set
{
if (Equals(base.BaseValue, value))
_isHidden = value;
OnVisibilityChanged();
}
}
/// <summary>
/// Gets the profile element (such as layer or folder) this property is applied to
/// </summary>
public RenderProfileElement ProfileElement { get; internal set; }
/// <summary>
/// The parent group of this layer property, set after construction
/// </summary>
public LayerPropertyGroup LayerPropertyGroup { get; internal set; }
#endregion
#region Value management
private T _baseValue;
/// <summary>
/// Called every update (if keyframes are both supported and enabled) to determine the new <see cref="CurrentValue" />
/// based on the provided progress
/// </summary>
/// <param name="keyframeProgress">The linear current keyframe progress</param>
/// <param name="keyframeProgressEased">The current keyframe progress, eased with the current easing function</param>
protected virtual void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{
throw new NotImplementedException();
}
/// <summary>
/// Gets or sets the base value of this layer property without any keyframes applied
/// </summary>
public T BaseValue
{
get => _baseValue;
set
{
if (Equals(_baseValue, value))
return;
base.BaseValue = value;
Update();
_baseValue = value;
Update(0);
OnBaseValueChanged();
LayerPropertyGroup.OnLayerPropertyBaseValueChanged(new LayerPropertyEventArgs(this));
}
}
/// <summary>
/// Gets the current value of this property as it is affected by it's keyframes, updated once every frame
/// </summary>
public new T CurrentValue
{
get => base.CurrentValue != null ? (T) base.CurrentValue : default;
set => base.CurrentValue = value;
}
public T CurrentValue { get; set; }
/// <summary>
/// Gets or sets the default value of this layer property. If set, this value is automatically applied if the property
/// has no value in storage
/// </summary>
public new T DefaultValue
public T DefaultValue { get; set; }
/// <summary>
/// Sets the current value, using either keyframes if enabled or the base value.
/// </summary>
/// <param name="value">The value to set.</param>
/// <param name="time">
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
/// or existing keyframe.
/// </param>
public void SetCurrentValue(T value, TimeSpan? time)
{
get => base.DefaultValue != null ? (T) base.DefaultValue : default;
set => base.DefaultValue = value;
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (time == null || !KeyframesEnabled || !KeyframesSupported)
BaseValue = value;
else
{
// If on a keyframe, update the keyframe
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
// Create a new keyframe if none found
if (currentKeyframe == null)
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
else
currentKeyframe.Value = value;
}
// Force an update so that the base value is applied to the current value and
// keyframes/data bindings are applied using the new base value
Update(0);
}
/// <summary>
/// Overrides the property value with the default value
/// </summary>
public void ApplyDefaultValue()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
BaseValue = DefaultValue;
CurrentValue = DefaultValue;
}
#endregion
#region Keyframes
private bool _keyframesEnabled;
private List<LayerPropertyKeyframe<T>> _keyframes;
/// <summary>
/// Gets whether keyframes are supported on this type of property
/// </summary>
public bool KeyframesSupported { get; protected internal set; } = true;
/// <summary>
/// Gets or sets whether keyframes are enabled on this property, has no effect if <see cref="KeyframesSupported" /> is
/// False
/// </summary>
public bool KeyframesEnabled
{
get => _keyframesEnabled;
set
{
if (_keyframesEnabled == value) return;
_keyframesEnabled = value;
OnKeyframesToggled();
}
}
/// <summary>
/// Gets a read-only list of all the keyframes on this layer property
/// </summary>
@ -78,50 +216,22 @@ namespace Artemis.Core
/// </summary>
public LayerPropertyKeyframe<T> NextKeyframe { get; protected set; }
public override IReadOnlyList<BaseLayerPropertyKeyframe> BaseKeyframes => _keyframes.Cast<BaseLayerPropertyKeyframe>().ToList().AsReadOnly();
/// <summary>
/// Sets the current value, using either keyframes if enabled or the base value.
/// </summary>
/// <param name="value">The value to set.</param>
/// <param name="time">
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
/// or existing keyframe.
/// </param>
public void SetCurrentValue(T value, TimeSpan? time)
{
if (time == null || !KeyframesEnabled || !KeyframesSupported)
BaseValue = value;
else
{
// If on a keyframe, update the keyframe
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
// Create a new keyframe if none found
if (currentKeyframe == null)
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
else
currentKeyframe.Value = value;
}
// Force an update so that the base value is applied to the current value and
// keyframes/data bindings are applied using the new base value
Update();
}
/// <summary>
/// Adds a keyframe to the layer property
/// </summary>
/// <param name="keyframe">The keyframe to add</param>
public void AddKeyframe(LayerPropertyKeyframe<T> keyframe)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (_keyframes.Contains(keyframe))
return;
keyframe.LayerProperty?.RemoveKeyframe(keyframe);
keyframe.LayerProperty = this;
keyframe.BaseLayerProperty = this;
_keyframes.Add(keyframe);
SortKeyframes();
OnKeyframeAdded();
}
@ -132,6 +242,9 @@ namespace Artemis.Core
/// <param name="keyframe">The keyframe to remove</param>
public LayerPropertyKeyframe<T> CopyKeyframe(LayerPropertyKeyframe<T> keyframe)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
var newKeyframe = new LayerPropertyKeyframe<T>(
keyframe.Value,
keyframe.Position,
@ -149,63 +262,18 @@ namespace Artemis.Core
/// <param name="keyframe">The keyframe to remove</param>
public void RemoveKeyframe(LayerPropertyKeyframe<T> keyframe)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (!_keyframes.Contains(keyframe))
return;
_keyframes.Remove(keyframe);
keyframe.LayerProperty = null;
keyframe.BaseLayerProperty = null;
SortKeyframes();
OnKeyframeRemoved();
}
/// <summary>
/// Removes all keyframes from the layer property
/// </summary>
public void ClearKeyframes()
{
var keyframes = new List<LayerPropertyKeyframe<T>>(_keyframes);
foreach (var layerPropertyKeyframe in keyframes)
RemoveKeyframe(layerPropertyKeyframe);
}
/// <inheritdoc />
public override void ApplyDefaultValue()
{
BaseValue = DefaultValue;
CurrentValue = DefaultValue;
}
/// <inheritdoc />
public override Type GetPropertyType()
{
return typeof(T);
}
/// <summary>
/// Called every update (if keyframes are both supported and enabled) to determine the new <see cref="CurrentValue" />
/// based on the provided progress
/// </summary>
/// <param name="keyframeProgress">The linear current keyframe progress</param>
/// <param name="keyframeProgressEased">The current keyframe progress, eased with the current easing function</param>
protected virtual void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{
throw new NotImplementedException();
}
/// <summary>
/// Updates the property, applying keyframes and data bindings to the current value
/// </summary>
internal void Update(double deltaTime = 0)
{
CurrentValue = BaseValue;
UpdateKeyframes();
UpdateDataBindings(deltaTime);
OnUpdated();
}
/// <summary>
/// Sorts the keyframes in ascending order by position
/// </summary>
@ -214,77 +282,6 @@ namespace Artemis.Core
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
}
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
{
// Doubt this will happen but let's make sure
if (_isInitialized)
throw new ArtemisCoreException("Layer property already initialized, wut");
PropertyEntity = entity;
LayerPropertyGroup = layerPropertyGroup;
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
try
{
if (entity.Value != null)
BaseValue = JsonConvert.DeserializeObject<T>(entity.Value);
IsLoadedFromStorage = fromStorage;
CurrentValue = BaseValue;
KeyframesEnabled = entity.KeyframesEnabled;
_keyframes.Clear();
_keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
JsonConvert.DeserializeObject<T>(k.Value),
k.Position,
(Easings.Functions) k.EasingFunction,
this
)));
_dataBindings.Clear();
foreach (var entityDataBindingEntity in entity.DataBindingEntities)
{
var dataBinding = new DataBinding(this, entityDataBindingEntity);
_dataBindings.Add(dataBinding);
}
}
catch (JsonException e)
{
// TODO: Properly log the JSON exception
Debug.WriteLine($"JSON exception while deserializing: {e}");
IsLoadedFromStorage = false;
}
finally
{
SortKeyframes();
_isInitialized = true;
}
}
internal override void ApplyToEntity()
{
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue);
PropertyEntity.KeyframesEnabled = KeyframesEnabled;
PropertyEntity.KeyframeEntities.Clear();
PropertyEntity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
{
Value = JsonConvert.SerializeObject(k.Value),
Position = k.Position,
EasingFunction = (int) k.EasingFunction
}));
PropertyEntity.DataBindingEntities.Clear();
foreach (var dataBinding in DataBindings)
{
dataBinding.ApplyToEntity();
PropertyEntity.DataBindingEntities.Add(dataBinding.Entity);
}
}
private void UpdateKeyframes()
{
if (!KeyframesSupported || !KeyframesEnabled)
@ -311,48 +308,269 @@ namespace Artemis.Core
}
}
#endregion
#region Data bindings
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda, DataBindingConverter converter)
{
// If the lambda references to itself, use the property info of public new T CurrentValue
PropertyInfo propertyInfo;
string path = null;
if (propertyLambda.Parameters[0] == propertyLambda.Body)
{
propertyInfo = GetType().GetProperties().FirstOrDefault(p => p.Name == nameof(CurrentValue) && p.PropertyType == typeof(T));
}
else
{
propertyInfo = ReflectionUtilities.GetPropertyInfo(CurrentValue, propertyLambda);
// Deconstruct the lambda
var current = (MemberExpression) propertyLambda.Body;
path = current.Member.Name;
while (current.Expression is MemberExpression memberExpression)
{
path = current.Member.Name + "." + path;
current = memberExpression;
}
}
internal readonly List<IDataBindingRegistration> _dataBindingRegistrations = new List<IDataBindingRegistration>();
internal readonly List<IDataBinding> _dataBindings = new List<IDataBinding>();
if (converter.SupportedType != propertyInfo.PropertyType)
/// <summary>
/// Gets whether data bindings are supported on this type of property
/// </summary>
public bool DataBindingsSupported { get; protected internal set; } = true;
public DataBindingRegistration<T, TProperty> GetDataBindingRegistration<TProperty>(string expression)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
var match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
registration.PropertyExpression.ToString() == expression);
return (DataBindingRegistration<T, TProperty>) match;
}
public List<IDataBindingRegistration> GetAllDataBindingRegistrations()
{
return _dataBindingRegistrations;
}
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter)
throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'");
if (converter.SupportedType != propertyExpression.ReturnType)
{
throw new ArtemisCoreException($"Cannot register data binding property for property {propertyInfo.Name} " +
throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " +
"because the provided converter does not support the property's type");
}
_dataBindingRegistrations.Add(new DataBindingRegistration(this, propertyInfo, converter, path));
_dataBindingRegistrations.Add(new DataBindingRegistration<T, TProperty>(this, converter, propertyExpression));
}
/// <summary>
/// Enables a data binding for the provided <paramref name="dataBindingRegistration" />
/// </summary>
/// <returns>The newly created data binding</returns>
public DataBinding<T, TProperty> EnableDataBinding<TProperty>(DataBindingRegistration<T, TProperty> dataBindingRegistration)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (dataBindingRegistration.LayerProperty != this)
throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property");
var dataBinding = new DataBinding<T, TProperty>(dataBindingRegistration);
_dataBindings.Add(dataBinding);
return dataBinding;
}
/// <summary>
/// Disables the provided data binding
/// </summary>
/// <param name="dataBinding">The data binding to remove</param>
public void DisableDataBinding<TProperty>(DataBinding<T, TProperty> dataBinding)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
_dataBindings.Remove(dataBinding);
}
private void UpdateDataBindings(double deltaTime)
{
foreach (var dataBinding in DataBindings)
foreach (var dataBinding in _dataBindings)
{
dataBinding.Update(deltaTime);
dataBinding.ApplyToProperty();
dataBinding.Apply();
}
}
#endregion
#region Storage
private bool _isInitialized;
/// <summary>
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
/// </summary>
public bool IsLoadedFromStorage { get; internal set; }
internal PropertyEntity Entity { get; set; }
/// <inheritdoc />
public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
_isInitialized = true;
ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement));
LayerPropertyGroup = group ?? throw new ArgumentNullException(nameof(group));
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage;
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
}
/// <inheritdoc />
public void Load()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
if (!IsLoadedFromStorage)
ApplyDefaultValue();
else
{
try
{
if (Entity.Value != null)
BaseValue = JsonConvert.DeserializeObject<T>(Entity.Value);
}
catch (JsonException e)
{
// ignored for now
}
}
CurrentValue = BaseValue;
KeyframesEnabled = Entity.KeyframesEnabled;
_keyframes.Clear();
try
{
_keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
JsonConvert.DeserializeObject<T>(k.Value),
k.Position,
(Easings.Functions) k.EasingFunction,
this
)));
}
catch (JsonException e)
{
// ignored for now
}
_dataBindings.Clear();
foreach (var dataBindingRegistration in _dataBindingRegistrations)
{
var dataBinding = dataBindingRegistration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
}
/// <summary>
/// Saves the property to the underlying property entity that was configured when calling
/// <see cref="ApplyToLayerProperty" />
/// </summary>
public void Save()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
Entity.Value = JsonConvert.SerializeObject(BaseValue);
Entity.KeyframesEnabled = KeyframesEnabled;
Entity.KeyframeEntities.Clear();
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
{
Value = JsonConvert.SerializeObject(k.Value),
Position = k.Position,
EasingFunction = (int) k.EasingFunction
}));
Entity.DataBindingEntities.Clear();
foreach (var dataBinding in _dataBindings)
dataBinding.Save();
}
#endregion
#region Events
/// <summary>
/// Occurs once every frame when the layer property is updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> Updated;
/// <summary>
/// Occurs when the base value of the layer property was updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> BaseValueChanged;
/// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> VisibilityChanged;
/// <summary>
/// Occurs when keyframes are enabled/disabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframesToggled;
/// <summary>
/// Occurs when a new keyframe was added to the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframeAdded;
/// <summary>
/// Occurs when a keyframe was removed from the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframeRemoved;
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnBaseValueChanged()
{
BaseValueChanged?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
#endregion
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
foreach (var dataBinding in _dataBindings)
dataBinding.Dispose();
}
}
}

View File

@ -1,16 +1,28 @@
using System;
using Stylet;
namespace Artemis.Core
{
public class LayerPropertyKeyframe<T> : BaseLayerPropertyKeyframe
/// <summary>
/// Represents a keyframe on a <see cref="LayerProperty{T}" /> containing a value and a timestamp
/// </summary>
public class LayerPropertyKeyframe<T> : PropertyChangedBase
{
private LayerProperty<T> _layerProperty;
private TimeSpan _position;
private T _value;
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
/// <summary>
/// Creates a new instance of the <see cref="LayerPropertyKeyframe{T}" /> class
/// </summary>
/// <param name="value">The value of the keyframe</param>
/// <param name="position">The position of this keyframe in the timeline</param>
/// <param name="easingFunction">The easing function applied on the value of the keyframe</param>
/// <param name="layerProperty">The layer property this keyframe is applied to</param>
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty)
{
_position = position;
Value = value;
LayerProperty = layerProperty;
EasingFunction = easingFunction;
@ -34,8 +46,11 @@ namespace Artemis.Core
set => SetAndNotify(ref _value, value);
}
/// <inheritdoc />
public override TimeSpan Position
/// <summary>
/// The position of this keyframe in the timeline
/// </summary>
public TimeSpan Position
{
get => _position;
set
@ -45,8 +60,15 @@ namespace Artemis.Core
}
}
/// <inheritdoc />
public override void Remove()
/// <summary>
/// The easing function applied on the value of the keyframe
/// </summary>
public Easings.Functions EasingFunction { get; set; }
/// <summary>
/// Removes the keyframe from the layer property
/// </summary>
public void Remove()
{
LayerProperty.RemoveKeyframe(this);
}

View File

@ -1,6 +1,4 @@
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core
namespace Artemis.Core
{
/// <inheritdoc />
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
@ -9,10 +7,12 @@ namespace Artemis.Core
{
KeyframesSupported = false;
DataBindingsSupported = false;
BaseValueChanged += OnBaseValueChanged;
}
/// <summary>
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient"/>
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
/// </summary>
public static implicit operator ColorGradient(ColorGradientLayerProperty p)
{
@ -25,12 +25,11 @@ namespace Artemis.Core
throw new ArtemisCoreException("Color Gradients do not support keyframes.");
}
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
private void OnBaseValueChanged(object sender, LayerPropertyEventArgs<ColorGradient> e)
{
base.ApplyToLayerProperty(entity, layerPropertyGroup, fromStorage);
// Don't allow color gradients to be null
BaseValue ??= DefaultValue ?? new ColorGradient();
if (BaseValue == null)
BaseValue = DefaultValue ?? new ColorGradient();
}
}
}

View File

@ -7,11 +7,10 @@ namespace Artemis.Core
{
internal SKColorLayerProperty()
{
RegisterDataBindingProperty(color => color.Alpha, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Alpha));
RegisterDataBindingProperty(color => color.Red, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Red));
RegisterDataBindingProperty(color => color.Green, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Green));
RegisterDataBindingProperty(color => color.Blue, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Blue));
RegisterDataBindingProperty(color => color.Hue, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Hue));
RegisterDataBindingProperty(color => color.Alpha, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Alpha));
RegisterDataBindingProperty(color => color.Red, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Red));
RegisterDataBindingProperty(color => color.Green, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Green));
RegisterDataBindingProperty(color => color.Blue, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Blue));
}
/// <summary>

View File

@ -7,8 +7,8 @@ namespace Artemis.Core
{
internal SKPointLayerProperty()
{
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter());
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter());
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter<SKPoint>());
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter<SKPoint>());
}
/// <summary>

View File

@ -7,8 +7,8 @@ namespace Artemis.Core
{
internal SKSizeLayerProperty()
{
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter());
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter());
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>());
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter<SKSize>());
}
/// <summary>

View File

@ -2,54 +2,58 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.Core.Properties;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Humanizer;
namespace Artemis.Core
{
public abstract class LayerPropertyGroup : IDisposable
{
private readonly List<BaseLayerProperty> _layerProperties;
private readonly List<ILayerProperty> _layerProperties;
private readonly List<LayerPropertyGroup> _layerPropertyGroups;
private ReadOnlyCollection<BaseLayerProperty> _allLayerProperties;
private bool _disposed;
private bool _isHidden;
protected LayerPropertyGroup()
{
_layerProperties = new List<BaseLayerProperty>();
_layerProperties = new List<ILayerProperty>();
_layerPropertyGroups = new List<LayerPropertyGroup>();
}
/// <summary>
/// Gets the profile element (such as layer or folder) this effect is applied to
/// Gets the description of this group
/// </summary>
public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; }
/// <summary>
/// Gets the info of the plugin this group is associated with
/// </summary>
public PluginInfo PluginInfo { get; internal set; }
/// <summary>
/// Gets the profile element (such as layer or folder) this group is associated with
/// </summary>
public RenderProfileElement ProfileElement { get; internal set; }
/// <summary>
/// The parent group of this group
/// </summary>
public LayerPropertyGroup Parent { get; internal set; }
/// <summary>
/// The path of this property group
/// </summary>
public string Path { get; internal set; }
/// <summary>
/// The parent group of this layer property group, set after construction
/// </summary>
public LayerPropertyGroup Parent { get; internal set; }
/// <summary>
/// Gets whether this property groups properties are all initialized
/// </summary>
public bool PropertiesInitialized { get; private set; }
/// <summary>
/// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID
/// </summary>
public bool IsCorePropertyGroup { get; internal set; }
public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; }
/// <summary>
/// The layer brush this property group belongs to
/// </summary>
@ -76,37 +80,45 @@ namespace Artemis.Core
/// <summary>
/// A list of all layer properties in this group
/// </summary>
public ReadOnlyCollection<BaseLayerProperty> LayerProperties => _layerProperties.AsReadOnly();
public ReadOnlyCollection<ILayerProperty> LayerProperties => _layerProperties.AsReadOnly();
/// <summary>
/// A list of al child groups in this group
/// </summary>
public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups => _layerPropertyGroups.AsReadOnly();
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DisableProperties();
foreach (var layerProperty in _layerProperties)
layerProperty.Dispose();
foreach (var layerPropertyGroup in _layerPropertyGroups)
layerPropertyGroup.Dispose();
}
#endregion
/// <summary>
/// Recursively gets all layer properties on this group and any subgroups
/// </summary>
/// <returns></returns>
public IReadOnlyCollection<BaseLayerProperty> GetAllLayerProperties()
public IReadOnlyCollection<ILayerProperty> GetAllLayerProperties()
{
if (!PropertiesInitialized)
return new List<BaseLayerProperty>();
if (_allLayerProperties != null)
return _allLayerProperties;
if (_disposed)
throw new ObjectDisposedException("LayerPropertyGroup");
var result = new List<BaseLayerProperty>(LayerProperties);
if (!PropertiesInitialized)
return new List<ILayerProperty>();
var result = new List<ILayerProperty>(LayerProperties);
foreach (var layerPropertyGroup in LayerPropertyGroups)
result.AddRange(layerPropertyGroup.GetAllLayerProperties());
_allLayerProperties = result.AsReadOnly();
return _allLayerProperties;
return result.AsReadOnly();
}
/// <summary>
@ -116,7 +128,7 @@ namespace Artemis.Core
protected abstract void PopulateDefaults();
/// <summary>
/// Called when the property group is deactivated
/// Called when the property group is aactivated
/// </summary>
protected abstract void EnableProperties();
@ -125,19 +137,26 @@ namespace Artemis.Core
/// </summary>
protected abstract void DisableProperties();
/// <summary>
/// Called when the property group and all its layer properties have been initialized
/// </summary>
protected virtual void OnPropertyGroupInitialized()
{
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty);
}
internal void InitializeProperties(IRenderElementService renderElementService, RenderProfileElement profileElement, [NotNull] string path)
internal void Initialize(RenderProfileElement profileElement, [NotNull] string path, PluginInfo pluginInfo)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (pluginInfo == null)
throw new ArgumentNullException(nameof(pluginInfo));
// Doubt this will happen but let's make sure
if (PropertiesInitialized)
throw new ArtemisCoreException("Layer property group already initialized, wut");
PluginInfo = pluginInfo;
ProfileElement = profileElement;
Path = path.TrimEnd('.');
@ -146,55 +165,21 @@ namespace Artemis.Core
{
var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
if (propertyDescription != null)
{
if (!typeof(BaseLayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path + propertyInfo.Name}");
var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true);
if (instance == null)
throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}");
instance.ProfileElement = profileElement;
instance.Parent = this;
instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription;
if (instance.PropertyDescription.DisableKeyframes)
instance.KeyframesSupported = false;
InitializeProperty(profileElement, path + propertyInfo.Name, instance);
propertyInfo.SetValue(this, instance);
_layerProperties.Add(instance);
}
InitializeProperty(propertyInfo, (PropertyDescriptionAttribute) propertyDescription);
else
{
var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
if (propertyGroupDescription != null)
{
if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup");
var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType);
if (instance == null)
throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}");
instance.Parent = this;
instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription;
instance.LayerBrush = LayerBrush;
instance.LayerEffect = LayerEffect;
instance.InitializeProperties(renderElementService, profileElement, $"{path}{propertyInfo.Name}.");
propertyInfo.SetValue(this, instance);
_layerPropertyGroups.Add(instance);
}
InitializeChildGroup(propertyInfo, (PropertyGroupDescriptionAttribute) propertyGroupDescription);
}
}
// Request the property group to populate defaults
PopulateDefaults();
// Apply the newly populated defaults
foreach (var layerProperty in _layerProperties.Where(p => !p.IsLoadedFromStorage))
layerProperty.ApplyDefaultValue();
// Load the layer properties after defaults have been applied
foreach (var layerProperty in _layerProperties)
layerProperty.Load();
EnableProperties();
PropertiesInitialized = true;
@ -206,25 +191,11 @@ namespace Artemis.Core
if (!PropertiesInitialized)
return;
// Get all properties with a PropertyDescriptionAttribute
foreach (var propertyInfo in GetType().GetProperties())
{
var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
if (propertyDescription != null)
{
var layerProperty = (BaseLayerProperty) propertyInfo.GetValue(this);
layerProperty.ApplyToEntity();
}
else
{
var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
if (propertyGroupDescription != null)
{
var layerPropertyGroup = (LayerPropertyGroup) propertyInfo.GetValue(this);
layerPropertyGroup.ApplyToEntity();
}
}
}
foreach (var layerProperty in LayerProperties)
layerProperty.Save();
foreach (var layerPropertyGroup in LayerPropertyGroups)
layerPropertyGroup.ApplyToEntity();
}
internal void Update(double deltaTime)
@ -234,32 +205,63 @@ namespace Artemis.Core
OnPropertyGroupUpdating(new LayerPropertyGroupUpdatingEventArgs(deltaTime));
}
private void InitializeProperty(RenderProfileElement profileElement, string path, BaseLayerProperty instance)
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
{
Guid pluginGuid;
if (IsCorePropertyGroup || instance.IsCoreProperty)
pluginGuid = Constants.CorePluginInfo.Guid;
else if (instance.Parent.LayerBrush != null)
pluginGuid = instance.Parent.LayerBrush.PluginInfo.Guid;
else
pluginGuid = instance.Parent.LayerEffect.PluginInfo.Guid;
var path = Path + ".";
var entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path);
var fromStorage = true;
if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path + propertyInfo.Name}");
var instance = (ILayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true);
if (instance == null)
throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}");
// Ensure the description has a name, if not this is a good point to set it based on the property info
if (string.IsNullOrWhiteSpace(propertyDescription.Name))
propertyDescription.Name = propertyInfo.Name.Humanize();
var entity = GetPropertyEntity(ProfileElement, path + propertyInfo.Name, out var fromStorage);
instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription);
propertyInfo.SetValue(this, instance);
_layerProperties.Add(instance);
}
private void InitializeChildGroup(PropertyInfo propertyInfo, PropertyGroupDescriptionAttribute propertyGroupDescription)
{
var path = Path + ".";
if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup");
var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType);
if (instance == null)
throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}");
// Ensure the description has a name, if not this is a good point to set it based on the property info
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Name))
propertyGroupDescription.Name = propertyInfo.Name.Humanize();
instance.Parent = this;
instance.GroupDescription = propertyGroupDescription;
instance.LayerBrush = LayerBrush;
instance.LayerEffect = LayerEffect;
instance.Initialize(ProfileElement, $"{path}{propertyInfo.Name}.", PluginInfo);
propertyInfo.SetValue(this, instance);
_layerPropertyGroups.Add(instance);
}
private PropertyEntity GetPropertyEntity(RenderProfileElement profileElement, string path, out bool fromStorage)
{
var entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == PluginInfo.Guid && p.Path == path);
fromStorage = entity != null;
if (entity == null)
{
fromStorage = false;
entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path};
entity = new PropertyEntity {PluginGuid = PluginInfo.Guid, Path = path};
profileElement.RenderElementEntity.PropertyEntities.Add(entity);
}
instance.ApplyToLayerProperty(entity, this, fromStorage);
instance.BaseValueChanged += InstanceOnBaseValueChanged;
}
private void InstanceOnBaseValueChanged(object sender, EventArgs e)
{
OnLayerPropertyBaseValueChanged(new LayerPropertyEventArgs((BaseLayerProperty) sender));
return entity;
}
#region Events
@ -282,17 +284,17 @@ namespace Artemis.Core
/// </summary>
public event EventHandler VisibilityChanged;
protected virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e)
internal virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e)
{
PropertyGroupUpdating?.Invoke(this, e);
}
protected virtual void OnVisibilityChanged()
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnLayerPropertyBaseValueChanged(LayerPropertyEventArgs e)
internal virtual void OnLayerPropertyBaseValueChanged(LayerPropertyEventArgs e)
{
LayerPropertyBaseValueChanged?.Invoke(this, e);
}

View File

@ -16,17 +16,19 @@ namespace Artemis.Core
ProfileEntity = new ProfileEntity();
EntityId = Guid.NewGuid();
Profile = this;
Module = module;
Name = name;
UndoStack = new Stack<string>();
RedoStack = new Stack<string>();
AddChild(new Folder(this, this, "Root folder"));
ApplyToEntity();
var _ = new Folder(this, "Root folder");
Save();
}
internal Profile(ProfileModule module, ProfileEntity profileEntity)
{
Profile = this;
ProfileEntity = profileEntity;
EntityId = profileEntity.Id;
@ -34,7 +36,7 @@ namespace Artemis.Core
UndoStack = new Stack<string>();
RedoStack = new Stack<string>();
ApplyToProfile();
Load();
}
public ProfileModule Module { get; }
@ -86,7 +88,7 @@ namespace Artemis.Core
return (Folder) Children.Single();
}
public void ApplyToProfile()
internal override void Load()
{
if (_disposed)
throw new ObjectDisposedException("Profile");
@ -103,7 +105,9 @@ namespace Artemis.Core
// Populate the profile starting at the root, the rest is populated recursively
var rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId);
if (rootFolder == null)
AddChild(new Folder(this, this, "Root folder"));
{
var _ = new Folder(this, "Root folder");
}
else
AddChild(new Folder(this, this, rootFolder));
}
@ -129,7 +133,7 @@ namespace Artemis.Core
_disposed = true;
}
internal override void ApplyToEntity()
internal override void Save()
{
if (_disposed)
throw new ObjectDisposedException("Profile");
@ -140,7 +144,7 @@ namespace Artemis.Core
ProfileEntity.IsActive = IsActivated;
foreach (var profileElement in Children)
profileElement.ApplyToEntity();
profileElement.Save();
ProfileEntity.Folders.Clear();
ProfileEntity.Folders.AddRange(GetAllFolders().Select(f => f.FolderEntity));

View File

@ -23,18 +23,27 @@ namespace Artemis.Core
ChildrenList = new List<ProfileElement>();
}
/// <summary>
/// Gets the unique ID of this profile element
/// </summary>
public Guid EntityId
{
get => _entityId;
internal set => SetAndNotify(ref _entityId, value);
}
/// <summary>
/// Gets the profile this element belongs to
/// </summary>
public Profile Profile
{
get => _profile;
internal set => SetAndNotify(ref _profile, value);
}
/// <summary>
/// Gets the parent of this element
/// </summary>
public ProfileElement Parent
{
get => _parent;
@ -73,12 +82,6 @@ namespace Artemis.Core
set => SetAndNotify(ref _enabled, value);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Updates the element
/// </summary>
@ -90,39 +93,13 @@ namespace Artemis.Core
/// </summary>
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
public List<Folder> GetAllFolders()
/// <inheritdoc />
public override string ToString()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
var folders = new List<Folder>();
foreach (var childFolder in Children.Where(c => c is Folder).Cast<Folder>())
{
// Add all folders in this element
folders.Add(childFolder);
// Add all folders in folders inside this element
folders.AddRange(childFolder.GetAllFolders());
}
return folders;
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
}
public List<Layer> GetAllLayers()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
var layers = new List<Layer>();
// Add all layers in this element
layers.AddRange(Children.Where(c => c is Layer).Cast<Layer>());
// Add all layers in folders inside this element
foreach (var childFolder in Children.Where(c => c is Folder).Cast<Folder>())
layers.AddRange(childFolder.GetAllLayers());
return layers;
}
#region Hierarchy
/// <summary>
/// Adds a profile element to the <see cref="Children" /> collection, optionally at the given position (1-based)
@ -133,9 +110,12 @@ namespace Artemis.Core
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList)
{
if (ChildrenList.Contains(child))
return;
// Add to the end of the list
if (order == null)
{
@ -189,9 +169,64 @@ namespace Artemis.Core
OnChildRemoved();
}
public override string ToString()
/// <summary>
/// Returns a flattened list of all child folders
/// </summary>
/// <returns></returns>
public List<Folder> GetAllFolders()
{
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
var folders = new List<Folder>();
foreach (var childFolder in Children.Where(c => c is Folder).Cast<Folder>())
{
// Add all folders in this element
folders.Add(childFolder);
// Add all folders in folders inside this element
folders.AddRange(childFolder.GetAllFolders());
}
return folders;
}
/// <summary>
/// Returns a flattened list of all child layers
/// </summary>
/// <returns></returns>
public List<Layer> GetAllLayers()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
var layers = new List<Layer>();
// Add all layers in this element
layers.AddRange(Children.Where(c => c is Layer).Cast<Layer>());
// Add all layers in folders inside this element
foreach (var childFolder in Children.Where(c => c is Folder).Cast<Folder>())
layers.AddRange(childFolder.GetAllLayers());
return layers;
}
#endregion
#region Storage
internal abstract void Load();
internal abstract void Save();
#endregion
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
@ -201,10 +236,7 @@ namespace Artemis.Core
}
}
/// <summary>
/// Applies the profile element's properties to the underlying storage entity
/// </summary>
internal abstract void ApplyToEntity();
#endregion
#region Events

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.LayerEffects;
using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
@ -12,36 +13,33 @@ namespace Artemis.Core
{
public abstract class RenderProfileElement : ProfileElement
{
/// <summary>
/// Returns a list of all keyframes on all properties and effects of this layer
/// </summary>
public virtual List<BaseLayerPropertyKeyframe> GetAllKeyframes()
protected RenderProfileElement()
{
var keyframes = new List<BaseLayerPropertyKeyframe>();
foreach (var layerEffect in LayerEffects)
{
foreach (var baseLayerProperty in layerEffect.BaseProperties.GetAllLayerProperties())
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
}
return keyframes;
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
}
protected void ApplyRenderElementDefaults()
internal void ApplyRenderElementDefaults()
{
MainSegmentLength = TimeSpan.FromSeconds(5);
}
protected void ApplyRenderElementEntity()
internal void LoadRenderElement()
{
StartSegmentLength = RenderElementEntity.StartSegmentLength;
MainSegmentLength = RenderElementEntity.MainSegmentLength;
EndSegmentLength = RenderElementEntity.EndSegmentLength;
DisplayContinuously = RenderElementEntity.DisplayContinuously;
AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline;
DisplayConditionGroup = RenderElementEntity.RootDisplayCondition != null
? new DisplayConditionGroup(null, RenderElementEntity.RootDisplayCondition)
: new DisplayConditionGroup(null);
ActivateEffects();
}
protected void ApplyRenderElementToEntity()
internal void SaveRenderElement()
{
RenderElementEntity.StartSegmentLength = StartSegmentLength;
RenderElementEntity.MainSegmentLength = MainSegmentLength;
@ -55,8 +53,8 @@ namespace Artemis.Core
var layerEffectEntity = new LayerEffectEntity
{
Id = layerEffect.EntityId,
PluginGuid = layerEffect.PluginInfo.Guid,
EffectType = layerEffect.GetType().Name,
PluginGuid = layerEffect.Descriptor.PlaceholderFor ?? layerEffect.PluginInfo.Guid,
EffectType = layerEffect.GetEffectTypeName(),
Name = layerEffect.Name,
Enabled = layerEffect.Enabled,
HasBeenRenamed = layerEffect.HasBeenRenamed,
@ -65,6 +63,10 @@ namespace Artemis.Core
RenderElementEntity.LayerEffects.Add(layerEffectEntity);
layerEffect.BaseProperties.ApplyToEntity();
}
// Conditions
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
DisplayConditionGroup?.Save();
}
#region Properties
@ -211,7 +213,6 @@ namespace Artemis.Core
return (TimelinePosition - oldPosition).TotalSeconds;
}
/// <summary>
/// Overrides the progress of the element
/// </summary>
@ -221,7 +222,7 @@ namespace Artemis.Core
#endregion
#region Effects
#region Effect management
protected List<BaseLayerEffect> _layerEffects;
@ -230,13 +231,45 @@ namespace Artemis.Core
/// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => _layerEffects.AsReadOnly();
internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect)
/// <summary>
/// Adds a the layer effect described inthe provided <paramref name="descriptor" />
/// </summary>
public void AddLayerEffect(LayerEffectDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
var entity = new LayerEffectEntity
{
Id = Guid.NewGuid(),
Enabled = true,
Order = LayerEffects.Count + 1
};
descriptor.CreateInstance(this, entity);
OrderEffects();
OnLayerEffectsUpdated();
}
/// <summary>
/// Removes the provided layer
/// </summary>
/// <param name="effect"></param>
public void RemoveLayerEffect([NotNull] BaseLayerEffect effect)
{
if (effect == null) throw new ArgumentNullException(nameof(effect));
DeactivateLayerEffect(effect);
// Remove the effect from the layer and dispose it
_layerEffects.Remove(effect);
effect.Dispose();
// Update the order on the remaining effects
OrderEffects();
OnLayerEffectsUpdated();
}
private void OrderEffects()
{
var index = 0;
foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order))
{
@ -244,23 +277,69 @@ namespace Artemis.Core
index++;
}
_layerEffects.Sort((a, b) => a.Order.CompareTo(b.Order));
}
internal void ActivateEffects()
{
foreach (var layerEffectEntity in RenderElementEntity.LayerEffects)
{
// If there is a non-placeholder existing effect, skip this entity
var existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id);
if (existing != null && existing.Descriptor.PlaceholderFor == null)
continue;
var descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor;
if (descriptor != null)
{
// If a descriptor is found but there is an existing placeholder, remove the placeholder
if (existing != null)
{
_layerEffects.Remove(existing);
existing.Dispose();
}
// Create an instance with the descriptor
descriptor.CreateInstance(this, layerEffectEntity);
}
else if (existing == null)
{
// If no descriptor was found and there was no existing placeholder, create a placeholder
descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.PluginGuid);
descriptor.CreateInstance(this, layerEffectEntity);
}
}
OrderEffects();
}
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
{
_layerEffects.Add(layerEffect);
OnLayerEffectsUpdated();
}
internal void AddLayerEffect([NotNull] BaseLayerEffect effect)
private void LayerEffectStoreOnLayerEffectRemoved(object sender, LayerEffectStoreEvent e)
{
if (effect == null) throw new ArgumentNullException(nameof(effect));
_layerEffects.Add(effect);
OnLayerEffectsUpdated();
// If effects provided by the plugin are on the element, replace them with placeholders
var pluginEffects = _layerEffects.Where(ef => ef.Descriptor.LayerEffectProvider != null &&
ef.PluginInfo.Guid == e.Registration.Plugin.PluginInfo.Guid).ToList();
foreach (var pluginEffect in pluginEffects)
{
var entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.EntityId);
_layerEffects.Remove(pluginEffect);
pluginEffect.Dispose();
var descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.PluginInfo.Guid);
descriptor.CreateInstance(this, entity);
}
}
internal void DeactivateLayerEffect([NotNull] BaseLayerEffect effect)
private void LayerEffectStoreOnLayerEffectAdded(object sender, LayerEffectStoreEvent e)
{
if (effect == null) throw new ArgumentNullException(nameof(effect));
// Remove the effect from the layer and dispose it
_layerEffects.Remove(effect);
effect.Dispose();
if (RenderElementEntity.LayerEffects.Any(ef => ef.PluginGuid == e.Registration.Plugin.PluginInfo.Guid))
ActivateEffects();
}
#endregion
@ -301,6 +380,21 @@ namespace Artemis.Core
#endregion
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (var baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
#region Events
public event EventHandler LayerEffectsUpdated;
@ -311,17 +405,5 @@ namespace Artemis.Core
}
#endregion
/// <summary>
/// Returns all the layer properties of this profile element
/// </summary>
public virtual List<BaseLayerProperty> GetAllLayerProperties()
{
var result = new List<BaseLayerProperty>();
foreach (var baseLayerEffect in LayerEffects)
result.AddRange(baseLayerEffect.BaseProperties.GetAllLayerProperties());
return result;
}
}
}

View File

@ -22,6 +22,12 @@ namespace Artemis.Core.DataModelExpansions
[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; }
public bool ContainsPath(string path)
{
var parts = path.Split('.');

View File

@ -10,7 +10,8 @@ namespace Artemis.Core.DataModelExpansions
public abstract class DataModelExpansion<T> : BaseDataModelExpansion where T : DataModel
{
/// <summary>
/// The data model driving this module
/// The main data model of this data model expansion
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
/// </summary>
public T DataModel
{

View File

@ -105,7 +105,7 @@ namespace Artemis.Core.LayerBrushes
// Not only is this needed to initialize properties on the layer brushes, it also prevents implementing anything
// but LayerBrush<T> and RgbNetLayerBrush<T> outside the core
internal abstract void Initialize(IRenderElementService renderElementService);
internal abstract void Initialize();
internal abstract void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint);

View File

@ -1,5 +1,4 @@
using System;
using Artemis.Core.Services;
namespace Artemis.Core.LayerBrushes
{
@ -33,11 +32,12 @@ namespace Artemis.Core.LayerBrushes
internal set => _properties = value;
}
internal void InitializeProperties(IRenderElementService renderElementService)
internal void InitializeProperties()
{
Properties = Activator.CreateInstance<T>();
Properties.GroupDescription ??= new PropertyGroupDescriptionAttribute {Name = Descriptor.DisplayName, Description = Descriptor.Description};
Properties.LayerBrush = this;
Properties.InitializeProperties(renderElementService, Layer, "LayerBrush.");
Properties.Initialize(Layer, "LayerBrush.", PluginInfo);
PropertiesInitialized = true;
EnableLayerBrush();

View File

@ -26,9 +26,9 @@ namespace Artemis.Core.LayerBrushes
Render(canvas, canvasInfo, path, paint);
}
internal override void Initialize(IRenderElementService renderElementService)
internal override void Initialize()
{
InitializeProperties(renderElementService);
InitializeProperties();
}
}
}

View File

@ -1,4 +1,6 @@
using System;
using Artemis.Core.Services;
using Ninject;
namespace Artemis.Core.LayerBrushes
{
@ -41,5 +43,30 @@ namespace Artemis.Core.LayerBrushes
/// The plugin that provided this <see cref="LayerBrushDescriptor" />
/// </summary>
public LayerBrushProvider LayerBrushProvider { get; }
/// <summary>
/// Creates an instance of the described brush and applies it to the layer
/// </summary>
internal void CreateInstance(Layer layer)
{
if (layer.LayerBrush != null)
throw new ArtemisCoreException("Layer already has an instantiated layer brush");
var brush = (BaseLayerBrush) CoreService.Kernel.Get(LayerBrushType);
brush.Layer = layer;
brush.Descriptor = this;
brush.Initialize();
brush.Update(0);
layer.LayerBrush = brush;
layer.OnLayerBrushUpdated();
}
public bool MatchesLayerBrushReference(LayerBrushReference reference)
{
if (reference == null)
return false;
return LayerBrushProvider.PluginInfo.Guid == reference.BrushPluginGuid && LayerBrushType.Name == reference.BrushType;
}
}
}

View File

@ -41,11 +41,14 @@ namespace Artemis.Core.LayerBrushes
if (!Enabled)
throw new ArtemisPluginException(PluginInfo, "Can only add a layer brush descriptor when the plugin is enabled");
_layerBrushDescriptors.Add(new LayerBrushDescriptor(displayName, description, icon, typeof(T), this));
var descriptor = new LayerBrushDescriptor(displayName, description, icon, typeof(T), this);
_layerBrushDescriptors.Add(descriptor);
LayerBrushStore.Add(descriptor);
}
private void OnPluginDisabled(object sender, EventArgs e)
{
// The store will clean up the registrations by itself, the plugin just needs to clear its own list
_layerBrushDescriptors.Clear();
}
}

View File

@ -70,9 +70,9 @@ namespace Artemis.Core.LayerBrushes
}
}
internal override void Initialize(IRenderElementService renderElementService)
internal override void Initialize()
{
InitializeProperties(renderElementService);
InitializeProperties();
}
}
}

View File

@ -46,12 +46,12 @@ namespace Artemis.Core.LayerBrushes
LedGroup.Brush = GetBrush();
}
internal override void Initialize(IRenderElementService renderElementService)
internal override void Initialize()
{
LedGroup = new ListLedGroup();
Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated;
InitializeProperties(renderElementService);
InitializeProperties();
UpdateLedGroup();
}

View File

@ -95,7 +95,7 @@ namespace Artemis.Core.LayerEffects
/// <summary>
/// Gets the plugin info that defined this effect
/// </summary>
public PluginInfo PluginInfo => Descriptor.LayerEffectProvider.PluginInfo;
public PluginInfo PluginInfo => Descriptor.LayerEffectProvider?.PluginInfo;
/// <summary>
/// Gets a reference to the layer property group without knowing it's type
@ -108,7 +108,7 @@ namespace Artemis.Core.LayerEffects
public void Dispose()
{
DisableLayerEffect();
BaseProperties.Dispose();
BaseProperties?.Dispose();
}
/// <summary>
@ -147,6 +147,8 @@ namespace Artemis.Core.LayerEffects
// Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything
// but LayerEffect<T> outside the core
internal abstract void Initialize(IRenderElementService renderElementService);
internal abstract void Initialize();
internal virtual string GetEffectTypeName() => GetType().Name;
}
}

View File

@ -30,19 +30,19 @@ namespace Artemis.Core.LayerEffects
internal set => _properties = value;
}
internal void InitializeProperties(IRenderElementService renderElementService)
internal void InitializeProperties()
{
Properties = Activator.CreateInstance<T>();
Properties.LayerEffect = this;
Properties.InitializeProperties(renderElementService, ProfileElement, PropertyRootPath);
Properties.Initialize(ProfileElement, PropertyRootPath, PluginInfo);
PropertiesInitialized = true;
EnableLayerEffect();
}
internal override void Initialize(IRenderElementService renderElementService)
internal override void Initialize()
{
InitializeProperties(renderElementService);
InitializeProperties();
}
}
}

View File

@ -1,4 +1,9 @@
using System;
using System.Linq;
using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Ninject;
namespace Artemis.Core.LayerEffects
{
@ -41,5 +46,46 @@ namespace Artemis.Core.LayerEffects
/// The plugin that provided this <see cref="LayerEffectDescriptor" />
/// </summary>
public LayerEffectProvider LayerEffectProvider { get; }
/// <summary>
/// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder
/// </summary>
public Guid? PlaceholderFor { get; internal set; }
/// <summary>
/// Creates an instance of the described effect and applies it to the render element
/// </summary>
internal void CreateInstance(RenderProfileElement renderElement, LayerEffectEntity entity)
{
// Skip effects already on the element
if (renderElement.LayerEffects.Any(e => e.EntityId == entity.Id))
return;
if (PlaceholderFor != null)
{
CreatePlaceHolderInstance(renderElement, entity);
return;
}
var effect = (BaseLayerEffect)CoreService.Kernel.Get(LayerEffectType);
effect.ProfileElement = renderElement;
effect.EntityId = entity.Id;
effect.Order = entity.Order;
effect.Name = entity.Name;
effect.Enabled = entity.Enabled;
effect.Descriptor = this;
effect.Initialize();
effect.Update(0);
renderElement.ActivateLayerEffect(effect);
}
private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity)
{
var effect = new PlaceholderLayerEffect(entity, PlaceholderFor.Value) {ProfileElement = renderElement, Descriptor = this};
effect.Initialize();
renderElement.ActivateLayerEffect(effect);
}
}
}

View File

@ -41,11 +41,14 @@ namespace Artemis.Core.LayerEffects
if (!Enabled)
throw new ArtemisPluginException(PluginInfo, "Can only add a layer effect descriptor when the plugin is enabled");
_layerEffectDescriptors.Add(new LayerEffectDescriptor(displayName, description, icon, typeof(T), this));
var descriptor = new LayerEffectDescriptor(displayName, description, icon, typeof(T), this);
_layerEffectDescriptors.Add(descriptor);
LayerEffectStore.Add(descriptor);
}
private void OnPluginDisabled(object sender, EventArgs e)
{
// The store will clean up the registrations by itself, the plugin just needs to clear its own list
_layerEffectDescriptors.Clear();
}
}

View File

@ -0,0 +1,75 @@
using System;
using Artemis.Storage.Entities.Profile;
using SkiaSharp;
namespace Artemis.Core.LayerEffects.Placeholder
{
/// <summary>
/// Represents a layer effect that could not be loaded due to a missing plugin
/// </summary>
internal class PlaceholderLayerEffect : LayerEffect<PlaceholderProperties>
{
internal PlaceholderLayerEffect(LayerEffectEntity originalEntity, Guid placeholderFor)
{
OriginalEntity = originalEntity;
PlaceholderFor = placeholderFor;
EntityId = OriginalEntity.Id;
Order = OriginalEntity.Order;
Name = OriginalEntity.Name;
Enabled = OriginalEntity.Enabled;
HasBeenRenamed = OriginalEntity.HasBeenRenamed;
}
internal LayerEffectEntity OriginalEntity { get; }
public Guid PlaceholderFor { get; }
/// <inheritdoc />
public override void EnableLayerEffect()
{
}
/// <inheritdoc />
public override void DisableLayerEffect()
{
}
/// <inheritdoc />
public override void Update(double deltaTime)
{
}
/// <inheritdoc />
public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint)
{
}
/// <inheritdoc />
public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint)
{
}
internal override string GetEffectTypeName()
{
return OriginalEntity.EffectType;
}
}
/// <summary>
/// This is in place so that the UI has something to show
/// </summary>
internal class PlaceholderProperties : LayerPropertyGroup
{
protected override void PopulateDefaults()
{
}
protected override void EnableProperties()
{
}
protected override void DisableProperties()
{
}
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace Artemis.Core.LayerEffects.Placeholder
{
internal static class PlaceholderLayerEffectDescriptor
{
public static LayerEffectDescriptor Create(Guid missingPluginGuid)
{
var descriptor = new LayerEffectDescriptor("Missing effect", "This effect could not be loaded", "FileQuestion", null, Constants.EffectPlaceholderPlugin)
{
PlaceholderFor = missingPluginGuid
};
return descriptor;
}
}
}

View File

@ -14,6 +14,7 @@ namespace Artemis.Core.Modules
{
/// <summary>
/// The data model driving this module
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
/// </summary>
public T DataModel
{

View File

@ -18,6 +18,7 @@ namespace Artemis.Core.Modules
{
/// <summary>
/// The data model driving this module
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
/// </summary>
public T DataModel
{
@ -118,7 +119,7 @@ namespace Artemis.Core.Modules
/// Indicates whether or not a profile change is being animated
/// </summary>
public bool AnimatingProfileChange { get; private set; }
/// <summary>
/// Called after the profile has updated
/// </summary>

View File

@ -16,7 +16,7 @@ namespace Artemis.Core
/// <summary>
/// Gets whether the plugin is enabled
/// </summary>
public bool Enabled { get; private set; }
public bool Enabled { get; internal set; }
/// <summary>
/// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins

View File

@ -9,6 +9,7 @@ using Artemis.Core.JsonConverters;
using Artemis.Core.Ninject;
using Artemis.Storage;
using Newtonsoft.Json;
using Ninject;
using RGB.NET.Core;
using Serilog;
using Serilog.Events;
@ -22,6 +23,8 @@ namespace Artemis.Core.Services
/// </summary>
internal class CoreService : ICoreService
{
internal static IKernel Kernel;
private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting<LogEventLevel> _loggingLevel;
@ -34,9 +37,10 @@ namespace Artemis.Core.Services
private List<Module> _modules;
// ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else
public CoreService(ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService,
public CoreService(IKernel kernel, ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService,
IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService)
{
Kernel = kernel;
_logger = logger;
_pluginService = pluginService;
_rgbService = rgbService;
@ -77,6 +81,8 @@ namespace Artemis.Core.Services
_logger.Information("Initializing Artemis Core version {version}", versionAttribute?.InformationalVersion);
ApplyLoggingLevel();
DeserializationLogger.Initialize(Kernel);
// Initialize the services
_pluginService.CopyBuiltInPlugins();
_pluginService.LoadPlugins(StartupArguments.Contains("--ignore-plugin-lock"));

View File

@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services
{
internal class DataBindingService : IDataBindingService
{
private readonly ILogger _logger;
private readonly List<DataBindingModifierType> _registeredDataBindingModifierTypes;
public DataBindingService(ILogger logger)
{
_logger = logger;
_registeredDataBindingModifierTypes = new List<DataBindingModifierType>();
RegisterBuiltInModifiers();
}
public IReadOnlyCollection<DataBindingModifierType> RegisteredDataBindingModifierTypes
{
get
{
lock (_registeredDataBindingModifierTypes)
{
return _registeredDataBindingModifierTypes.AsReadOnly();
}
}
}
public void RegisterModifierType(PluginInfo pluginInfo, DataBindingModifierType dataBindingModifierType)
{
if (pluginInfo == null)
throw new ArgumentNullException(nameof(pluginInfo));
if (dataBindingModifierType == null)
throw new ArgumentNullException(nameof(dataBindingModifierType));
lock (_registeredDataBindingModifierTypes)
{
if (_registeredDataBindingModifierTypes.Contains(dataBindingModifierType))
return;
dataBindingModifierType.Register(pluginInfo, this);
_registeredDataBindingModifierTypes.Add(dataBindingModifierType);
}
}
public void RemoveModifierType(DataBindingModifierType dataBindingModifierType)
{
if (dataBindingModifierType == null)
throw new ArgumentNullException(nameof(dataBindingModifierType));
lock (_registeredDataBindingModifierTypes)
{
if (!_registeredDataBindingModifierTypes.Contains(dataBindingModifierType))
return;
dataBindingModifierType.Unsubscribe();
_registeredDataBindingModifierTypes.Remove(dataBindingModifierType);
}
}
public List<DataBindingModifierType> GetCompatibleModifierTypes(Type type)
{
lock (_registeredDataBindingModifierTypes)
{
if (type == null)
return new List<DataBindingModifierType>(_registeredDataBindingModifierTypes);
var candidates = _registeredDataBindingModifierTypes.Where(c => c.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList();
// If there are multiple modifier types with the same description, use the closest match
foreach (var dataBindingModifierTypes in candidates.GroupBy(c => c.Description).Where(g => g.Count() > 1).ToList())
{
var bestCandidate = dataBindingModifierTypes.OrderByDescending(c => c.CompatibleTypes.Contains(type)).FirstOrDefault();
foreach (var dataBindingModifierType in dataBindingModifierTypes)
{
if (dataBindingModifierType != bestCandidate)
candidates.Remove(dataBindingModifierType);
}
}
return candidates;
}
}
public DataBindingModifierType GetModifierType(Guid modifierTypePluginGuid, string modifierType)
{
return RegisteredDataBindingModifierTypes.FirstOrDefault(o => o.PluginInfo.Guid == modifierTypePluginGuid && o.GetType().Name == modifierType);
}
public void LogModifierDeserializationFailure(DataBindingModifier dataBindingModifier, JsonSerializationException exception)
{
_logger.Warning(
exception,
"Failed to deserialize static parameter for operator {order}. {operatorType}",
dataBindingModifier.Entity.Order,
dataBindingModifier.Entity.ModifierType
);
}
private void RegisterBuiltInModifiers()
{
RegisterModifierType(Constants.CorePluginInfo, new MultiplicationModifierType());
RegisterModifierType(Constants.CorePluginInfo, new DivideModifierType());
RegisterModifierType(Constants.CorePluginInfo, new FloorModifierType());
}
}
}

View File

@ -1,252 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides access to the main data model
/// </summary>
internal class DataModelService : IDataModelService
{
private readonly List<DataModel> _dataModelExpansions;
private readonly ILogger _logger;
private readonly IPluginService _pluginService;
private readonly List<DisplayConditionOperator> _registeredConditionOperators;
public DataModelService(IPluginService pluginService, ILogger logger)
{
_pluginService = pluginService;
_logger = logger;
_dataModelExpansions = new List<DataModel>();
_registeredConditionOperators = new List<DisplayConditionOperator>();
_pluginService.PluginEnabled += PluginServiceOnPluginEnabled;
_pluginService.PluginDisabled += PluginServiceOnPluginDisabled;
RegisterBuiltInConditionOperators();
foreach (var module in _pluginService.GetPluginsOfType<Module>().Where(m => m.InternalExpandsMainDataModel))
AddModuleDataModel(module);
foreach (var dataModelExpansion in _pluginService.GetPluginsOfType<BaseDataModelExpansion>())
AddDataModelExpansionDataModel(dataModelExpansion);
}
public IReadOnlyCollection<DisplayConditionOperator> RegisteredConditionOperators
{
get
{
lock (_registeredConditionOperators)
{
return _registeredConditionOperators.AsReadOnly();
}
}
}
public IReadOnlyCollection<DataModel> DataModelExpansions
{
get
{
lock (_dataModelExpansions)
{
return new List<DataModel>(_dataModelExpansions).AsReadOnly();
}
}
}
public void AddExpansion(DataModel dataModelExpansion)
{
lock (_dataModelExpansions)
{
_dataModelExpansions.Add(dataModelExpansion);
// TODO SpoinkyNL 3-3-2018: Initialize the expansion and fire an event
}
}
public void RemoveExpansion(DataModel dataModelExpansion)
{
lock (_dataModelExpansions)
{
if (!_dataModelExpansions.Contains(dataModelExpansion))
throw new ArtemisCoreException("Cannot remove a data model expansion that wasn't previously added.");
// TODO SpoinkyNL 3-3-2018: Dispose the expansion and fire an event
_dataModelExpansions.Remove(dataModelExpansion);
}
}
public DataModel GetPluginDataModel(Plugin plugin)
{
if (plugin is Module module)
return module.InternalDataModel;
if (plugin is BaseDataModelExpansion dataModelExpansion)
return dataModelExpansion.InternalDataModel;
return null;
}
public DataModel GetPluginDataModelByGuid(Guid pluginGuid)
{
var pluginInfo = _pluginService.GetAllPluginInfo().FirstOrDefault(i => i.Guid == pluginGuid);
if (pluginInfo == null || !pluginInfo.Enabled)
return null;
return GetPluginDataModel(pluginInfo.Instance);
}
public bool GetPluginExtendsDataModel(Plugin plugin)
{
if (plugin is Module module)
return module.InternalExpandsMainDataModel;
if (plugin is BaseDataModelExpansion)
return true;
return false;
}
public void RegisterConditionOperator(PluginInfo pluginInfo, DisplayConditionOperator displayConditionOperator)
{
if (pluginInfo == null)
throw new ArgumentNullException(nameof(pluginInfo));
if (displayConditionOperator == null)
throw new ArgumentNullException(nameof(displayConditionOperator));
lock (_registeredConditionOperators)
{
if (_registeredConditionOperators.Contains(displayConditionOperator))
return;
displayConditionOperator.Register(pluginInfo, this);
_registeredConditionOperators.Add(displayConditionOperator);
}
}
public void RemoveConditionOperator(DisplayConditionOperator displayConditionOperator)
{
if (displayConditionOperator == null)
throw new ArgumentNullException(nameof(displayConditionOperator));
lock (_registeredConditionOperators)
{
if (!_registeredConditionOperators.Contains(displayConditionOperator))
return;
displayConditionOperator.Unsubscribe();
_registeredConditionOperators.Remove(displayConditionOperator);
}
}
public List<DisplayConditionOperator> GetCompatibleConditionOperators(Type type)
{
lock (_registeredConditionOperators)
{
if (type == null)
return new List<DisplayConditionOperator>(_registeredConditionOperators);
var candidates = _registeredConditionOperators.Where(c => c.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList();
// If there are multiple operators with the same description, use the closest match
foreach (var displayConditionOperators in candidates.GroupBy(c => c.Description).Where(g => g.Count() > 1).ToList())
{
var bestCandidate = displayConditionOperators.OrderByDescending(c => c.CompatibleTypes.Contains(type)).FirstOrDefault();
foreach (var displayConditionOperator in displayConditionOperators)
{
if (displayConditionOperator != bestCandidate)
candidates.Remove(displayConditionOperator);
}
}
return candidates;
}
}
public DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType)
{
return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType);
}
public void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception)
{
_logger.Warning(
exception,
"Failed to deserialize display condition predicate {left} {operator} {right}",
displayConditionPredicate.Entity.LeftPropertyPath,
displayConditionPredicate.Entity.OperatorType,
displayConditionPredicate.Entity.RightPropertyPath
);
}
public void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionPredicate, JsonException exception)
{
_logger.Warning(
exception,
"Failed to deserialize display condition list predicate {list} => {left} {operator} {right}",
displayConditionPredicate.Entity.ListPropertyPath,
displayConditionPredicate.Entity.LeftPropertyPath,
displayConditionPredicate.Entity.OperatorType,
displayConditionPredicate.Entity.RightPropertyPath
);
}
private void RegisterBuiltInConditionOperators()
{
// General usage for any type
RegisterConditionOperator(Constants.CorePluginInfo, new EqualsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new NotEqualConditionOperator());
// Numeric operators
RegisterConditionOperator(Constants.CorePluginInfo, new LessThanConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new LessThanOrEqualConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanOrEqualConditionOperator());
// String operators
RegisterConditionOperator(Constants.CorePluginInfo, new StringEqualsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNotEqualConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringContainsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNullConditionOperator());
}
private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e)
{
if (e.PluginInfo.Instance is Module module && module.InternalExpandsMainDataModel)
AddModuleDataModel(module);
else if (e.PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion)
AddDataModelExpansionDataModel(dataModelExpansion);
}
private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion)
{
if (dataModelExpansion.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null");
AddExpansion(dataModelExpansion.InternalDataModel);
}
private void AddModuleDataModel(Module module)
{
if (module.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null");
AddExpansion(module.InternalDataModel);
}
private void PluginServiceOnPluginDisabled(object sender, PluginEventArgs e)
{
// Remove all data models related to the plugin
lock (_dataModelExpansions)
{
var toRemove = _dataModelExpansions.Where(d => d.PluginInfo == e.PluginInfo).ToList();
foreach (var dataModel in toRemove)
_dataModelExpansions.Remove(dataModel);
}
}
}
}

View File

@ -3,6 +3,9 @@ using System.Collections.Generic;
namespace Artemis.Core.Services
{
/// <summary>
/// A service that initializes the Core and manages the render loop
/// </summary>
public interface ICoreService : IArtemisService, IDisposable
{
/// <summary>

View File

@ -1,91 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Properties;
using Newtonsoft.Json;
namespace Artemis.Core.Services
{
public interface IDataModelService : IArtemisService
{
/// <summary>
/// Gets a read-only collection of all registered condition operators
/// </summary>
IReadOnlyCollection<DisplayConditionOperator> RegisteredConditionOperators { get; }
/// <summary>
/// Gets a read-only collection of all registered data model expansions
/// </summary>
IReadOnlyCollection<DataModel> DataModelExpansions { get; }
/// <summary>
/// Add an expansion to the datamodel to be available for use after the next update
/// </summary>
/// <param name="baseDataModelExpansion"></param>
void AddExpansion(DataModel baseDataModelExpansion);
/// <summary>
/// Remove a previously added expansion so that it is no longer available and updated
/// </summary>
/// <param name="baseDataModelExpansion"></param>
void RemoveExpansion(DataModel baseDataModelExpansion);
/// <summary>
/// If found, returns the data model of the provided plugin
/// </summary>
/// <param name="plugin">Should be a module with a data model or a data model expansion</param>
DataModel GetPluginDataModel(Plugin plugin);
/// <summary>
/// If found, returns the data model of the provided plugin
/// </summary>
/// <param name="pluginGuid">Should be a module with a data model or a data model expansion</param>
DataModel GetPluginDataModelByGuid(Guid pluginGuid);
/// <summary>
/// Determines whether the given plugin expands the main data model
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
bool GetPluginExtendsDataModel(Plugin plugin);
/// <summary>
/// Registers a new condition operator for use in layer conditions
/// </summary>
/// <param name="pluginInfo">The PluginInfo of the plugin this condition operator belongs to</param>
/// <param name="displayConditionOperator">The condition operator to register</param>
void RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] DisplayConditionOperator displayConditionOperator);
/// <summary>
/// Removes a condition operator so it is no longer available for use in layer conditions
/// </summary>
/// <param name="displayConditionOperator">The layer condition operator to remove</param>
void RemoveConditionOperator([NotNull] DisplayConditionOperator displayConditionOperator);
/// <summary>
/// Returns all the display condition operators compatible with the provided type
/// </summary>
List<DisplayConditionOperator> GetCompatibleConditionOperators(Type type);
/// <summary>
/// Gets a condition operator by its plugin GUID and type name
/// </summary>
/// <param name="operatorPluginGuid">The operator's plugin GUID</param>
/// <param name="operatorType">The type name of the operator</param>
DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
/// <summary>
/// Logs a predicate deserialization failure
/// </summary>
/// <param name="displayConditionPredicate">The predicate that failed to deserialize</param>
/// <param name="exception">The JSON exception that occurred</param>
void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception);
/// <summary>
/// Logs a list predicate deserialization failure
/// </summary>
/// <param name="displayConditionListPredicate">The list predicate that failed to deserialize</param>
/// <param name="exception">The JSON exception that occurred</param>
void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionListPredicate, JsonException exception);
}
}

View File

@ -1,5 +1,8 @@
namespace Artemis.Core.Services
{
/// <summary>
/// A service that allows you manage an <see cref="ArtemisDevice"/>
/// </summary>
public interface IDeviceService : IArtemisService
{
/// <summary>

View File

@ -1,59 +0,0 @@
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
namespace Artemis.Core.Services
{
public interface IRenderElementService : IArtemisService
{
/// <summary>
/// Creates a new layer
/// </summary>
/// <param name="profile"></param>
/// <param name="parent"></param>
/// <param name="name"></param>
/// <returns></returns>
Layer CreateLayer(Profile profile, ProfileElement parent, string name);
/// <summary>
/// Removes the currently active layer brush from the <see cref="Layer" /> and deletes any settings
/// </summary>
/// <param name="layer">The layer to remove the active brush from</param>
void RemoveLayerBrush(Layer layer);
/// <summary>
/// Deactivates the currently active layer brush from the <see cref="Layer" /> but keeps all settings
/// </summary>
/// <param name="layer">The layer to deactivate the active brush on</param>
void DeactivateLayerBrush(Layer layer);
/// <summary>
/// Instantiates and adds the <see cref="BaseLayerBrush" /> described by the provided
/// <see cref="LayerBrushDescriptor" />
/// to the <see cref="Layer" />.
/// </summary>
/// <param name="layer">The layer to instantiate the brush for</param>
/// <returns></returns>
BaseLayerBrush InstantiateLayerBrush(Layer layer);
/// <summary>
/// Instantiates and adds the <see cref="BaseLayerEffect" /> described by the provided
/// <see cref="LayerEffectDescriptor" /> to the <see cref="Layer" />.
/// </summary>
/// <param name="renderProfileElement">The layer/folder to instantiate the effect for</param>
void InstantiateLayerEffects(RenderProfileElement renderProfileElement);
/// <summary>
/// Adds the <see cref="BaseLayerEffect" /> described by the provided <see cref="LayerEffectDescriptor" /> to the
/// <see cref="Layer" />.
/// </summary>
/// <param name="renderProfileElement">The layer/folder to instantiate the effect for</param>
/// <param name="layerEffectDescriptor"></param>
/// <returns></returns>
BaseLayerEffect AddLayerEffect(RenderProfileElement renderProfileElement, LayerEffectDescriptor layerEffectDescriptor);
void RemoveLayerEffect(BaseLayerEffect layerEffect);
void InstantiateDisplayConditions(RenderProfileElement renderElement);
void InstantiateDataBindings(RenderProfileElement renderElement);
}
}

View File

@ -4,7 +4,10 @@ using RGB.NET.Core;
namespace Artemis.Core.Services
{
public interface IRgbService : IArtemisService
/// <summary>
/// A service that allows you to manage the <see cref="RGBSurface" /> and its contents
/// </summary>
public interface IRgbService : IArtemisService, IDisposable
{
/// <summary>
/// Gets or sets the RGB surface rendering is performed on
@ -26,7 +29,14 @@ namespace Artemis.Core.Services
/// </summary>
IReadOnlyCollection<IRGBDevice> LoadedDevices { get; }
/// <summary>
/// Gets the update trigger that drives the render loop
/// </summary>
TimerUpdateTrigger UpdateTrigger { get; }
/// <summary>
/// Gets or sets whether rendering should be paused
/// </summary>
bool IsRenderPaused { get; set; }
/// <summary>
@ -35,8 +45,6 @@ namespace Artemis.Core.Services
/// <param name="deviceProvider"></param>
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
void Dispose();
/// <summary>
/// Occurs when a single device has loaded
/// </summary>
@ -47,6 +55,9 @@ namespace Artemis.Core.Services
/// </summary>
event EventHandler<DeviceEventArgs> DeviceReloaded;
/// <summary>
/// Recalculates the LED group used by the <see cref="BitmapBrush" />
/// </summary>
void UpdateSurfaceLedGroup();
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core.Services
{
internal class ConditionOperatorService : IConditionOperatorService
{
public ConditionOperatorService()
{
RegisterBuiltInConditionOperators();
}
public ConditionOperatorRegistration RegisterConditionOperator(PluginInfo pluginInfo, ConditionOperator conditionOperator)
{
if (pluginInfo == null)
throw new ArgumentNullException(nameof(pluginInfo));
if (conditionOperator == null)
throw new ArgumentNullException(nameof(conditionOperator));
conditionOperator.PluginInfo = pluginInfo;
return ConditionOperatorStore.Add(conditionOperator);
}
public void RemoveConditionOperator(ConditionOperatorRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
ConditionOperatorStore.Remove(registration);
}
public List<ConditionOperator> GetConditionOperatorsForType(Type type)
{
return ConditionOperatorStore.GetForType(type).Select(r => r.ConditionOperator).ToList();
}
public ConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType)
{
return ConditionOperatorStore.Get(operatorPluginGuid, operatorType)?.ConditionOperator;
}
private void RegisterBuiltInConditionOperators()
{
// General usage for any type
RegisterConditionOperator(Constants.CorePluginInfo, new EqualsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new NotEqualConditionOperator());
// Numeric operators
RegisterConditionOperator(Constants.CorePluginInfo, new LessThanConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new LessThanOrEqualConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanOrEqualConditionOperator());
// String operators
RegisterConditionOperator(Constants.CorePluginInfo, new StringEqualsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNotEqualConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringContainsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNullConditionOperator());
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core.Services
{
internal class DataBindingService : IDataBindingService
{
public DataBindingService()
{
RegisterBuiltInModifiers();
}
public DataBindingModifierTypeRegistration RegisterModifierType(PluginInfo pluginInfo, DataBindingModifierType dataBindingModifierType)
{
if (pluginInfo == null)
throw new ArgumentNullException(nameof(pluginInfo));
if (dataBindingModifierType == null)
throw new ArgumentNullException(nameof(dataBindingModifierType));
dataBindingModifierType.PluginInfo = pluginInfo;
return DataBindingModifierTypeStore.Add(dataBindingModifierType);
}
public void RemoveModifierType(DataBindingModifierTypeRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
DataBindingModifierTypeStore.Remove(registration);
}
public List<DataBindingModifierType> GetCompatibleModifierTypes(Type type)
{
return DataBindingModifierTypeStore.GetForType(type).Select(r => r.DataBindingModifierType).ToList();
}
public DataBindingModifierType GetModifierType(Guid modifierTypePluginGuid, string modifierType)
{
return DataBindingModifierTypeStore.Get(modifierTypePluginGuid, modifierType)?.DataBindingModifierType;
}
private void RegisterBuiltInModifiers()
{
RegisterModifierType(Constants.CorePluginInfo, new MultiplicationModifierType());
RegisterModifierType(Constants.CorePluginInfo, new DivideModifierType());
RegisterModifierType(Constants.CorePluginInfo, new FloorModifierType());
}
}
}

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
namespace Artemis.Core.Services
{
internal class DataModelService : IDataModelService
{
public DataModelService(IPluginService pluginService)
{
// Add data models of already loaded plugins
foreach (var module in pluginService.GetPluginsOfType<Module>())
AddModuleDataModel(module);
foreach (var dataModelExpansion in pluginService.GetPluginsOfType<BaseDataModelExpansion>())
AddDataModelExpansionDataModel(dataModelExpansion);
// Add data models of new plugins when they get enabled
pluginService.PluginEnabled += PluginServiceOnPluginEnabled;
}
public DataModelRegistration RegisterDataModel(DataModel dataModel)
{
if (dataModel == null)
throw new ArgumentNullException(nameof(dataModel));
return DataModelStore.Add(dataModel);
}
public void RemoveDataModel(DataModelRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
DataModelStore.Remove(registration);
}
public List<DataModel> GetDataModels()
{
return DataModelStore.GetAll().Select(d => d.DataModel).ToList();
}
public T GetDataModel<T>() where T : DataModel
{
return (T) DataModelStore.GetAll().FirstOrDefault(d => d.DataModel is T)?.DataModel;
}
public DataModel GetPluginDataModel(Plugin plugin)
{
return DataModelStore.Get(plugin.PluginInfo.Guid)?.DataModel;
}
public DataModel GetPluginDataModel(Guid pluginGuid)
{
return DataModelStore.Get(pluginGuid)?.DataModel;
}
private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e)
{
if (e.PluginInfo.Instance is Module module)
AddModuleDataModel(module);
else if (e.PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion)
AddDataModelExpansionDataModel(dataModelExpansion);
}
private void AddModuleDataModel(Module module)
{
if (module.InternalDataModel == null)
return;
if (module.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null");
module.InternalDataModel.IsExpansion = module.InternalExpandsMainDataModel;
RegisterDataModel(module.InternalDataModel);
}
private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion)
{
if (dataModelExpansion.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null");
dataModelExpansion.InternalDataModel.IsExpansion = true;
RegisterDataModel(dataModelExpansion.InternalDataModel);
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Properties;
namespace Artemis.Core.Services
{
/// <summary>
/// A service that allows you to register and retrieve conditions operators used by display conditions
/// </summary>
public interface IConditionOperatorService : IArtemisService
{
/// <summary>
/// Registers a new condition operator for use in layer conditions
/// </summary>
/// <param name="pluginInfo">The PluginInfo of the plugin this condition operator belongs to</param>
/// <param name="conditionOperator">The condition operator to register</param>
ConditionOperatorRegistration RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] ConditionOperator conditionOperator);
/// <summary>
/// Removes a condition operator so it is no longer available for use in layer conditions
/// </summary>
/// <param name="registration">The registration of the condition operator to remove</param>
void RemoveConditionOperator([NotNull] ConditionOperatorRegistration registration);
/// <summary>
/// Returns all the condition operators compatible with the provided type
/// </summary>
List<ConditionOperator> GetConditionOperatorsForType(Type type);
/// <summary>
/// Gets a condition operator by its plugin GUID and type name
/// </summary>
/// <param name="operatorPluginGuid">The operator's plugin GUID</param>
/// <param name="operatorType">The type name of the operator</param>
ConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
}
}

View File

@ -1,29 +1,26 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Properties;
using Newtonsoft.Json;
namespace Artemis.Core.Services
{
/// <summary>
/// A service that allows you to register and retrieve data binding modifiers used by the data bindings system
/// </summary>
public interface IDataBindingService : IArtemisService
{
/// <summary>
/// Gets a read-only collection of all registered modifier types
/// </summary>
IReadOnlyCollection<DataBindingModifierType> RegisteredDataBindingModifierTypes { get; }
/// <summary>
/// Registers a new modifier type for use in data bindings
/// </summary>
/// <param name="pluginInfo">The PluginInfo of the plugin this modifier type belongs to</param>
/// <param name="dataBindingModifierType">The modifier type to register</param>
void RegisterModifierType([NotNull] PluginInfo pluginInfo, [NotNull] DataBindingModifierType dataBindingModifierType);
DataBindingModifierTypeRegistration RegisterModifierType([NotNull] PluginInfo pluginInfo, [NotNull] DataBindingModifierType dataBindingModifierType);
/// <summary>
/// Removes a modifier type so it is no longer available for use in data bindings
/// </summary>
/// <param name="dataBindingModifierType">The modifier type to remove</param>
void RemoveModifierType([NotNull] DataBindingModifierType dataBindingModifierType);
/// <param name="dataBindingModifierType">The registration of the modifier type to remove</param>
void RemoveModifierType([NotNull] DataBindingModifierTypeRegistration dataBindingModifierType);
/// <summary>
/// Returns all the data binding modifier types compatible with the provided type
@ -37,12 +34,5 @@ namespace Artemis.Core.Services
/// <param name="modifierType">The type name of the modifier type</param>
/// <returns></returns>
DataBindingModifierType GetModifierType(Guid modifierTypePluginGuid, string modifierType);
/// <summary>
/// Logs a modifier deserialization failure
/// </summary>
/// <param name="dataBindingModifier">The modifier that failed to deserialize</param>
/// <param name="exception">The JSON exception that occurred</param>
void LogModifierDeserializationFailure(DataBindingModifier dataBindingModifier, JsonSerializationException exception);
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core.Services
{
/// <summary>
/// A service that allows you to register and retrieve data models
/// </summary>
public interface IDataModelService : IArtemisService
{
/// <summary>
/// Add a data model to so that it is available to conditions and data bindings
/// </summary>
/// <param name="dataModel"></param>
DataModelRegistration RegisterDataModel(DataModel dataModel);
/// <summary>
/// Remove a previously added data model so that it is no longer available
/// </summary>
void RemoveDataModel(DataModelRegistration registration);
/// <summary>
/// Returns a list of all registered data models
/// </summary>
List<DataModel> GetDataModels();
/// <summary>
/// If found, returns the registered data model of type <typeparamref name="T" />
/// </summary>
/// <typeparam name="T">The type of the data model to find</typeparam>
T GetDataModel<T>() where T : DataModel;
/// <summary>
/// If found, returns the data model of the provided plugin
/// </summary>
/// <param name="plugin">The plugin to find the data model of</param>
DataModel GetPluginDataModel(Plugin plugin);
/// <summary>
/// If found, returns the data model of the provided plugin GUID
/// </summary>
/// <param name="pluginGuid">The GUID of the plugin to find the data model of</param>
DataModel GetPluginDataModel(Guid pluginGuid);
}
}

View File

@ -0,0 +1,31 @@
using System.Collections.Generic;
using Artemis.Core.LayerBrushes;
namespace Artemis.Core.Services
{
/// <summary>
/// A service that allows you to register and retrieve layer brushes
/// </summary>
public interface ILayerBrushService : IArtemisService
{
/// <summary>
/// Add a layer brush descriptor so that it is available to layers
/// </summary>
LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor);
/// <summary>
/// Remove a previously added layer brush descriptor so that it is no longer available
/// </summary>
void RemoveLayerBrush(LayerBrushRegistration registration);
/// <summary>
/// Returns a list of all registered layer brush descriptors
/// </summary>
List<LayerBrushDescriptor> GetLayerBrushes();
/// <summary>
/// Returns the descriptor of the default layer brush
/// </summary>
LayerBrushDescriptor GetDefaultLayerBrush();
}
}

Some files were not shown because too many files have changed in this diff Show More