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

Display conditions - Seperate list conditions into their own type

This commit is contained in:
Robert 2020-08-13 21:00:29 +02:00
parent f2f77da953
commit f359256ede
22 changed files with 692 additions and 218 deletions

View File

@ -12,19 +12,21 @@ namespace Artemis.Core.Models.Profile.Conditions
public DisplayConditionGroup(DisplayConditionPart parent) public DisplayConditionGroup(DisplayConditionPart parent)
{ {
Parent = parent; Parent = parent;
DisplayConditionGroupEntity = new DisplayConditionGroupEntity(); Entity = new DisplayConditionGroupEntity();
} }
public DisplayConditionGroup(DisplayConditionPart parent, DisplayConditionGroupEntity entity) public DisplayConditionGroup(DisplayConditionPart parent, DisplayConditionGroupEntity entity)
{ {
Parent = parent; Parent = parent;
DisplayConditionGroupEntity = entity; Entity = entity;
BooleanOperator = (BooleanOperator) DisplayConditionGroupEntity.BooleanOperator; BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
foreach (var childEntity in DisplayConditionGroupEntity.Children) foreach (var childEntity in Entity.Children)
{ {
if (childEntity is DisplayConditionGroupEntity groupEntity) if (childEntity is DisplayConditionGroupEntity groupEntity)
AddChild(new DisplayConditionGroup(this, groupEntity)); AddChild(new DisplayConditionGroup(this, groupEntity));
else if (childEntity is DisplayConditionListEntity listEntity)
AddChild(new DisplayConditionList(this, listEntity));
else if (childEntity is DisplayConditionPredicateEntity predicateEntity) else if (childEntity is DisplayConditionPredicateEntity predicateEntity)
AddChild(new DisplayConditionPredicate(this, predicateEntity)); AddChild(new DisplayConditionPredicate(this, predicateEntity));
else if (childEntity is DisplayConditionListPredicateEntity listPredicateEntity) else if (childEntity is DisplayConditionListPredicateEntity listPredicateEntity)
@ -33,7 +35,7 @@ namespace Artemis.Core.Models.Profile.Conditions
} }
public BooleanOperator BooleanOperator { get; set; } public BooleanOperator BooleanOperator { get; set; }
public DisplayConditionGroupEntity DisplayConditionGroupEntity { get; set; } public DisplayConditionGroupEntity Entity { get; set; }
public override bool Evaluate() public override bool Evaluate()
{ {
@ -80,10 +82,10 @@ namespace Artemis.Core.Models.Profile.Conditions
internal override void ApplyToEntity() internal override void ApplyToEntity()
{ {
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator; Entity.BooleanOperator = (int) BooleanOperator;
DisplayConditionGroupEntity.Children.Clear(); Entity.Children.Clear();
DisplayConditionGroupEntity.Children.AddRange(Children.Select(c => c.GetEntity())); Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (var child in Children) foreach (var child in Children)
child.ApplyToEntity(); child.ApplyToEntity();
} }
@ -96,7 +98,7 @@ namespace Artemis.Core.Models.Profile.Conditions
internal override DisplayConditionPartEntity GetEntity() internal override DisplayConditionPartEntity GetEntity()
{ {
return DisplayConditionGroupEntity; return Entity;
} }
} }

View File

@ -0,0 +1,148 @@
using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.Exceptions;
using Artemis.Core.Models.Profile.Conditions.Abstract;
using Artemis.Core.Plugins.Abstract.DataModels;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Core.Models.Profile.Conditions
{
public class DisplayConditionList : DisplayConditionPart
{
public DisplayConditionList(DisplayConditionPart parent)
{
Parent = parent;
Entity = new DisplayConditionListEntity();
// There is always a child root group, add it
AddChild(new DisplayConditionGroup(this));
}
public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity)
{
Parent = parent;
Entity = entity;
ListOperator = (ListOperator) entity.ListOperator;
// There should only be one child and it should be a group
var rootGroup = Entity.Children.SingleOrDefault() as DisplayConditionGroupEntity;
if (rootGroup == null)
{
Entity.Children.Clear();
AddChild(new DisplayConditionGroup(this));
}
else
AddChild(new DisplayConditionGroup(this, rootGroup));
}
public DisplayConditionListEntity Entity { get; set; }
public ListOperator ListOperator { get; set; }
public DataModel ListDataModel { get; private set; }
public string ListPropertyPath { get; private set; }
public override bool Evaluate()
{
if (CompiledListAccessor == null)
return false;
return EvaluateObject(CompiledListAccessor(ListDataModel));
}
public override bool EvaluateObject(object target)
{
if (!(target is IList list))
return false;
var objectList = list.Cast<object>();
return ListOperator switch
{
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
ListOperator.Count => false,
_ => throw new ArgumentOutOfRangeException()
};
}
internal override void ApplyToEntity()
{
// Target list
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
Entity.ListPropertyPath = ListPropertyPath;
// Operator
Entity.ListOperator = (int) ListOperator;
// Children
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (var child in Children)
child.ApplyToEntity();
}
internal override DisplayConditionPartEntity GetEntity()
{
return Entity;
}
internal override void Initialize(IDataModelService dataModelService)
{
// Target list
if (Entity.ListDataModelGuid != null)
{
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value);
if (dataModel != null && dataModel.ContainsPath(Entity.ListPropertyPath))
UpdateList(dataModel, Entity.ListPropertyPath);
}
// Children
var rootGroup = (DisplayConditionGroup) Children.Single();
rootGroup.Initialize(dataModelService);
}
public void UpdateList(DataModel dataModel, string path)
{
if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null)
{
if (!dataModel.ContainsPath(path))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
if (dataModel.GetListTypeAtPath(path) == null)
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
}
ListDataModel = dataModel;
ListPropertyPath = path;
if (dataModel != null)
{
var parameter = Expression.Parameter(typeof(object), "listDataModel");
var accessor = path.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, dataModel.GetType()),
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
CompiledListAccessor = lambda.Compile();
}
}
public Func<object, IList> CompiledListAccessor { get; set; }
}
public enum ListOperator
{
Any,
All,
None,
Count
}
}

View File

@ -1,106 +1,47 @@
using System; using System;
using System.Collections;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using Artemis.Core.Exceptions; using Artemis.Core.Exceptions;
using Artemis.Core.Extensions;
using Artemis.Core.Models.Profile.Conditions.Abstract; using Artemis.Core.Models.Profile.Conditions.Abstract;
using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels;
using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Newtonsoft.Json;
namespace Artemis.Core.Models.Profile.Conditions namespace Artemis.Core.Models.Profile.Conditions
{ {
public class DisplayConditionListPredicate : DisplayConditionPart public class DisplayConditionListPredicate : DisplayConditionPart
{ {
public DisplayConditionListPredicate(DisplayConditionPart parent) public DisplayConditionListPredicate(DisplayConditionPart parent, PredicateType predicateType)
{ {
Parent = parent; Parent = parent;
DisplayConditionListPredicateEntity = new DisplayConditionListPredicateEntity(); PredicateType = predicateType;
Entity = new DisplayConditionListPredicateEntity();
// There is always a child root group, add it
AddChild(new DisplayConditionGroup(this));
} }
public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity) public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity)
{ {
Parent = parent; Parent = parent;
DisplayConditionListPredicateEntity = entity; Entity = entity;
ListOperator = (ListOperator) entity.ListOperator; PredicateType = (PredicateType) entity.PredicateType;
// There should only be one child and it should be a group
var rootGroup = DisplayConditionListPredicateEntity.Children.SingleOrDefault() as DisplayConditionGroupEntity;
if (rootGroup == null)
{
DisplayConditionListPredicateEntity.Children.Clear();
AddChild(new DisplayConditionGroup(this));
}
else
AddChild(new DisplayConditionGroup(this, rootGroup));
} }
public DisplayConditionListPredicateEntity DisplayConditionListPredicateEntity { get; set; } public DisplayConditionListPredicateEntity Entity { get; set; }
public ListOperator ListOperator { get; set; } public PredicateType PredicateType { get; set; }
public DisplayConditionOperator Operator { get; private set; }
public Type ListType { get; private set; }
public DataModel ListDataModel { get; private set; } public DataModel ListDataModel { get; private set; }
public string ListPropertyPath { get; private set; } public string ListPropertyPath { get; private set; }
public override bool Evaluate() public string LeftPropertyPath { get; private set; }
{ public string RightPropertyPath { get; private set; }
return EvaluateObject(CompiledListAccessor(ListDataModel)); public object RightStaticValue { get; private set; }
}
public override bool EvaluateObject(object target) public Func<object, bool> CompiledListPredicate { get; private set; }
{
if (!(target is IList list))
return false;
var objectList = list.Cast<object>();
return ListOperator switch
{
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
ListOperator.Count => false,
_ => throw new ArgumentOutOfRangeException()
};
}
internal override void ApplyToEntity()
{
// Target list
DisplayConditionListPredicateEntity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
DisplayConditionListPredicateEntity.ListPropertyPath = ListPropertyPath;
// Operator
DisplayConditionListPredicateEntity.ListOperator = (int) ListOperator;
// Children
DisplayConditionListPredicateEntity.Children.Clear();
DisplayConditionListPredicateEntity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (var child in Children)
child.ApplyToEntity();
}
internal override DisplayConditionPartEntity GetEntity()
{
return DisplayConditionListPredicateEntity;
}
internal override void Initialize(IDataModelService dataModelService)
{
// Target list
if (DisplayConditionListPredicateEntity.ListDataModelGuid != null)
{
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionListPredicateEntity.ListDataModelGuid.Value);
if (dataModel != null && dataModel.ContainsPath(DisplayConditionListPredicateEntity.ListPropertyPath))
UpdateList(dataModel, DisplayConditionListPredicateEntity.ListPropertyPath);
}
// Children
var rootGroup = (DisplayConditionGroup) Children.Single();
rootGroup.Initialize(dataModelService);
}
public void UpdateList(DataModel dataModel, string path) public void UpdateList(DataModel dataModel, string path)
{ {
@ -111,35 +52,322 @@ namespace Artemis.Core.Models.Profile.Conditions
if (dataModel != null) if (dataModel != null)
{ {
if (!dataModel.ContainsPath(path)) var listType = dataModel.GetListTypeAtPath(path);
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); if (listType == null)
if (dataModel.GetListTypeAtPath(path) == null) throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{path}'");
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
ListType = listType;
}
else
{
ListType = null;
} }
ListDataModel = dataModel; ListDataModel = dataModel;
ListPropertyPath = path; ListPropertyPath = path;
if (dataModel != null) if (!ListContainsInnerPath(LeftPropertyPath))
{ LeftPropertyPath = null;
var parameter = Expression.Parameter(typeof(object), "listDataModel"); if (!ListContainsInnerPath(RightPropertyPath))
var accessor = path.Split('.').Aggregate<string, Expression>( RightPropertyPath = null;
Expression.Convert(parameter, dataModel.GetType()),
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter); CreateExpression();
CompiledListAccessor = lambda.Compile(); }
public void UpdateLeftSide(string path)
{
if (!ListContainsInnerPath(path))
throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}");
LeftPropertyPath = path;
ValidateOperator();
ValidateRightSide();
CreateExpression();
}
public void UpdateRightSideDynamic(string path)
{
if (!ListContainsInnerPath(path))
throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}");
PredicateType = PredicateType.Dynamic;
RightPropertyPath = path;
CreateExpression();
}
public void UpdateRightSideStatic(object staticValue)
{
PredicateType = PredicateType.Static;
RightPropertyPath = null;
SetStaticValue(staticValue);
CreateExpression();
}
public void UpdateOperator(DisplayConditionOperator displayConditionOperator)
{
if (displayConditionOperator == null)
{
Operator = null;
return;
}
if (LeftPropertyPath == null)
{
Operator = displayConditionOperator;
return;
}
var leftType = GetTypeAtInnerPath(LeftPropertyPath);
if (displayConditionOperator.SupportsType(leftType))
Operator = displayConditionOperator;
CreateExpression();
}
private void CreateExpression()
{
CompiledListPredicate = null;
if (Operator == null)
return;
// If the operator does not support a right side, create a static expression because the right side will simply be null
if (PredicateType == PredicateType.Dynamic && Operator.SupportsRightSide)
CreateDynamicExpression();
CreateStaticExpression();
}
internal override void ApplyToEntity()
{
Entity.PredicateType = (int) PredicateType;
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;
}
public override bool Evaluate()
{
return false;
}
public override bool EvaluateObject(object target)
{
return CompiledListPredicate != null && CompiledListPredicate(target);
}
internal override void Initialize(IDataModelService dataModelService)
{
// Left side
if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath))
UpdateLeftSide(Entity.LeftPropertyPath);
// Operator
if (Entity.OperatorPluginGuid != null)
{
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
// Right side dynamic
if (PredicateType == PredicateType.Dynamic && Entity.RightPropertyPath != null)
{
if (ListContainsInnerPath(Entity.RightPropertyPath))
UpdateLeftSide(Entity.LeftPropertyPath);
}
// Right side static
else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null)
{
try
{
if (LeftPropertyPath != null)
{
// Use the left side type so JSON.NET has a better idea what to do
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
object rightSideValue;
try
{
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
dataModelService.LogListPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
UpdateRightSideStatic(rightSideValue);
}
else
{
// Hope for the best... we must infer the type from JSON now
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
}
}
catch (JsonException e)
{
dataModelService.LogListPredicateDeserializationFailure(this, e);
}
} }
} }
public Func<object, IList> CompiledListAccessor { get; set; } internal override DisplayConditionPartEntity GetEntity()
} {
return Entity;
}
public enum ListOperator private void ValidateOperator()
{ {
Any, if (LeftPropertyPath == null || Operator == null)
All, return;
None,
Count var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
if (!Operator.SupportsType(leftSideType))
Operator = null;
}
private void ValidateRightSide()
{
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
if (PredicateType == PredicateType.Dynamic)
{
if (RightPropertyPath == null)
return;
var rightSideType = GetTypeAtInnerPath(RightPropertyPath);
if (!leftSideType.IsCastableFrom(rightSideType))
UpdateRightSideDynamic(null);
}
else
{
if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType()))
UpdateRightSideStatic(RightStaticValue);
else
UpdateRightSideStatic(null);
}
}
private void SetStaticValue(object staticValue)
{
// If the left side is empty simply apply the value, any validation will wait
if (LeftPropertyPath == null)
{
RightStaticValue = staticValue;
return;
}
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == leftSideType)
RightStaticValue = staticValue;
else if (staticValue != null)
RightStaticValue = Convert.ChangeType(staticValue, leftSideType);
// If null create a default instance for value types or simply make it null for reference types
else if (leftSideType.IsValueType)
RightStaticValue = Activator.CreateInstance(leftSideType);
else
RightStaticValue = null;
}
private void CreateDynamicExpression()
{
if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null)
return;
// List accessors share the same parameter because a list always contains one item per entry
var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
var rightSideAccessor = CreateListAccessor(RightPropertyPath, leftSideParameter);
// A conversion may be required if the types differ
// This can cause issues if the DisplayConditionOperator wasn't accurate in it's supported types but that is not a concern here
if (rightSideAccessor.Type != leftSideAccessor.Type)
rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
CompiledListPredicate = lambda.Compile();
}
private void CreateStaticExpression()
{
if (LeftPropertyPath == null || Operator == null)
return;
// List accessors share the same parameter because a list always contains one item per entry
var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
// If the left side is a value type but the input is empty, this isn't a valid expression
if (leftSideAccessor.Type.IsValueType && RightStaticValue == null)
return;
// If the right side value is null, the constant type cannot be inferred and must be provided manually
var rightSideConstant = RightStaticValue != null
? Expression.Constant(RightStaticValue)
: Expression.Constant(null, leftSideAccessor.Type);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant);
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
CompiledListPredicate = lambda.Compile();
}
private Expression CreateListAccessor(string path, ParameterExpression listParameter)
{
return path.Split('.').Aggregate<string, Expression>(
Expression.Convert(listParameter, ListType), // Cast to the appropriate type
Expression.Property
);
}
public bool ListContainsInnerPath(string path)
{
if (ListType == null)
return false;
var parts = path.Split('.');
var current = ListType;
foreach (var part in parts)
{
var property = current.GetProperty(part);
current = property?.PropertyType;
if (property == null)
return false;
}
return true;
}
public Type GetTypeAtInnerPath(string path)
{
if (!ListContainsInnerPath(path))
return null;
var parts = path.Split('.');
var current = ListType;
Type result = null;
foreach (var part in parts)
{
var property = current.GetProperty(part);
current = property.PropertyType;
result = property.PropertyType;
}
return result;
}
} }
} }

View File

@ -18,17 +18,17 @@ namespace Artemis.Core.Models.Profile.Conditions
{ {
Parent = parent; Parent = parent;
PredicateType = predicateType; PredicateType = predicateType;
DisplayConditionPredicateEntity = new DisplayConditionPredicateEntity(); Entity = new DisplayConditionPredicateEntity();
} }
public DisplayConditionPredicate(DisplayConditionPart parent, DisplayConditionPredicateEntity entity) public DisplayConditionPredicate(DisplayConditionPart parent, DisplayConditionPredicateEntity entity)
{ {
Parent = parent; Parent = parent;
DisplayConditionPredicateEntity = entity; Entity = entity;
PredicateType = (PredicateType) entity.PredicateType; PredicateType = (PredicateType) entity.PredicateType;
} }
public DisplayConditionPredicateEntity DisplayConditionPredicateEntity { get; set; } public DisplayConditionPredicateEntity Entity { get; set; }
public PredicateType PredicateType { get; set; } public PredicateType PredicateType { get; set; }
public DisplayConditionOperator Operator { get; private set; } public DisplayConditionOperator Operator { get; private set; }
@ -38,6 +38,8 @@ namespace Artemis.Core.Models.Profile.Conditions
public DataModel RightDataModel { get; private set; } public DataModel RightDataModel { get; private set; }
public string RightPropertyPath { get; private set; } public string RightPropertyPath { get; private set; }
public object RightStaticValue { get; private set; } public object RightStaticValue { get; private set; }
public DataModel ListDataModel { get; private set; }
public string ListPropertyPath { get; private set; }
public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; } public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; }
public Func<DataModel, bool> CompiledStaticPredicate { get; private set; } public Func<DataModel, bool> CompiledStaticPredicate { get; private set; }
@ -135,16 +137,16 @@ namespace Artemis.Core.Models.Profile.Conditions
internal override void ApplyToEntity() internal override void ApplyToEntity()
{ {
DisplayConditionPredicateEntity.PredicateType = (int) PredicateType; Entity.PredicateType = (int) PredicateType;
DisplayConditionPredicateEntity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid; Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
DisplayConditionPredicateEntity.LeftPropertyPath = LeftPropertyPath; Entity.LeftPropertyPath = LeftPropertyPath;
DisplayConditionPredicateEntity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid;
DisplayConditionPredicateEntity.RightPropertyPath = RightPropertyPath; Entity.RightPropertyPath = RightPropertyPath;
DisplayConditionPredicateEntity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
DisplayConditionPredicateEntity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name; Entity.OperatorType = Operator?.GetType().Name;
} }
public override bool Evaluate() public override bool Evaluate()
@ -168,30 +170,30 @@ namespace Artemis.Core.Models.Profile.Conditions
internal override void Initialize(IDataModelService dataModelService) internal override void Initialize(IDataModelService dataModelService)
{ {
// Left side // Left side
if (DisplayConditionPredicateEntity.LeftDataModelGuid != null) if (Entity.LeftDataModelGuid != null)
{ {
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.LeftDataModelGuid.Value); var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.LeftDataModelGuid.Value);
if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.LeftPropertyPath)) if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, DisplayConditionPredicateEntity.LeftPropertyPath); UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
} }
// Operator // Operator
if (DisplayConditionPredicateEntity.OperatorPluginGuid != null) if (Entity.OperatorPluginGuid != null)
{ {
var conditionOperator = dataModelService.GetConditionOperator(DisplayConditionPredicateEntity.OperatorPluginGuid.Value, DisplayConditionPredicateEntity.OperatorType); var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
if (conditionOperator != null) if (conditionOperator != null)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }
// Right side dynamic // Right side dynamic
if (PredicateType == PredicateType.Dynamic && DisplayConditionPredicateEntity.RightDataModelGuid != null) if (PredicateType == PredicateType.Dynamic && Entity.RightDataModelGuid != null)
{ {
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.RightDataModelGuid.Value); var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.RightDataModelGuid.Value);
if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.RightPropertyPath)) if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, DisplayConditionPredicateEntity.RightPropertyPath); UpdateRightSide(dataModel, Entity.RightPropertyPath);
} }
// Right side static // Right side static
else if (PredicateType == PredicateType.Static && DisplayConditionPredicateEntity.RightStaticValue != null) else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null)
{ {
try try
{ {
@ -203,12 +205,12 @@ namespace Artemis.Core.Models.Profile.Conditions
try try
{ {
rightSideValue = JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue, leftSideType); rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
} }
// If deserialization fails, use the type's default // If deserialization fails, use the type's default
catch (JsonSerializationException e) catch (JsonSerializationException e)
{ {
dataModelService.LogDeserializationFailure(this, e); dataModelService.LogPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType); rightSideValue = Activator.CreateInstance(leftSideType);
} }
@ -217,7 +219,7 @@ namespace Artemis.Core.Models.Profile.Conditions
else else
{ {
// Hope for the best... // Hope for the best...
UpdateRightSide(JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue)); UpdateRightSide(JsonConvert.DeserializeObject(Entity.RightStaticValue));
} }
} }
catch (JsonReaderException) catch (JsonReaderException)
@ -230,7 +232,7 @@ namespace Artemis.Core.Models.Profile.Conditions
internal override DisplayConditionPartEntity GetEntity() internal override DisplayConditionPartEntity GetEntity()
{ {
return DisplayConditionPredicateEntity; return Entity;
} }
private void ValidateOperator() private void ValidateOperator()
@ -292,7 +294,7 @@ namespace Artemis.Core.Models.Profile.Conditions
if (LeftDataModel == null || RightDataModel == null || Operator == null) if (LeftDataModel == null || RightDataModel == null || Operator == null)
return; return;
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null; var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null;
Expression leftSideAccessor; Expression leftSideAccessor;
Expression rightSideAccessor; Expression rightSideAccessor;
@ -335,7 +337,7 @@ namespace Artemis.Core.Models.Profile.Conditions
if (LeftDataModel == null || Operator == null) if (LeftDataModel == null || Operator == null)
return; return;
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null; var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null;
Expression leftSideAccessor; Expression leftSideAccessor;
ParameterExpression leftSideParameter; ParameterExpression leftSideParameter;
@ -373,7 +375,7 @@ namespace Artemis.Core.Models.Profile.Conditions
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter) private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
{ {
var listType = dataModel.GetListTypeAtPath(path); var listType = dataModel.GetListTypeInPath(path);
if (listType != null) if (listType != null)
throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list"); throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list");
@ -386,7 +388,7 @@ namespace Artemis.Core.Models.Profile.Conditions
private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter) private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter)
{ {
var listType = dataModel.GetListTypeAtPath(path); var listType = dataModel.GetListTypeInPath(path);
if (listType == null) if (listType == null)
throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list"); throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list");

View File

@ -299,7 +299,7 @@ namespace Artemis.Core.Models.Profile
ApplyRenderElementToEntity(); ApplyRenderElementToEntity();
// Conditions // Conditions
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity; RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
DisplayConditionGroup?.ApplyToEntity(); DisplayConditionGroup?.ApplyToEntity();
} }

View File

@ -206,7 +206,7 @@ namespace Artemis.Core.Models.Profile
} }
// Conditions // Conditions
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity; RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
DisplayConditionGroup?.ApplyToEntity(); DisplayConditionGroup?.ApplyToEntity();
} }

View File

@ -31,14 +31,8 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
var current = GetType(); var current = GetType();
foreach (var part in parts) foreach (var part in parts)
{ {
var property = current.GetProperty(part); var property = current?.GetProperty(part);
current = property?.PropertyType;
// For lists, look into the list type instead of the list itself
if (property != null && typeof(IList).IsAssignableFrom(property.PropertyType))
current = property.PropertyType.GetGenericArguments()[0];
else
current = property?.PropertyType;
if (property == null) if (property == null)
return false; return false;
} }
@ -58,20 +52,14 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
foreach (var part in parts) foreach (var part in parts)
{ {
var property = current.GetProperty(part); var property = current.GetProperty(part);
current = property.PropertyType;
// For lists, look into the list type instead of the list itself
if (typeof(IList).IsAssignableFrom(property.PropertyType))
current = property.PropertyType.GetGenericArguments()[0];
else
current = property.PropertyType;
result = property.PropertyType; result = property.PropertyType;
} }
return result; return result;
} }
public Type GetListTypeAtPath(string path) public Type GetListTypeInPath(string path)
{ {
if (!ContainsPath(path)) if (!ContainsPath(path))
return null; return null;
@ -79,8 +67,13 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
var parts = path.Split('.'); var parts = path.Split('.');
var current = GetType(); var current = GetType();
var index = 0;
foreach (var part in parts) foreach (var part in parts)
{ {
// Only return a type if the path CONTAINS a list, not if it points TO a list
if (index == parts.Length - 1)
return null;
var property = current.GetProperty(part); var property = current.GetProperty(part);
// For lists, look into the list type instead of the list itself // For lists, look into the list type instead of the list itself
@ -88,14 +81,24 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
return property.PropertyType.GetGenericArguments()[0]; return property.PropertyType.GetGenericArguments()[0];
current = property.PropertyType; current = property.PropertyType;
index++;
} }
return null; return null;
} }
public Type GetListTypeAtPath(string path)
{
if (!ContainsPath(path))
return null;
var child = GetTypeAtPath(path);
return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null;
}
public string GetListInnerPath(string path) public string GetListInnerPath(string path)
{ {
if (GetListTypeAtPath(path) == null) if (GetListTypeInPath(path) == null)
throw new ArtemisCoreException($"Cannot determine inner list path at {path} because it does not contain a list"); throw new ArtemisCoreException($"Cannot determine inner list path at {path} because it does not contain a list");
var parts = path.Split('.'); var parts = path.Split('.');

View File

@ -178,9 +178,27 @@ namespace Artemis.Core.Services
return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType); return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType);
} }
public void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception) public void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception)
{ {
_logger.Warning(exception, "Failed to deserialize display condition predicate {predicate}", displayConditionPredicate); _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() private void RegisterBuiltInConditionOperators()

View File

@ -60,6 +60,7 @@ namespace Artemis.Core.Services.Interfaces
List<DisplayConditionOperator> GetCompatibleConditionOperators(Type type); List<DisplayConditionOperator> GetCompatibleConditionOperators(Type type);
DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType); DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception); void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception);
void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionListPredicate, JsonException exception);
} }
} }

View File

@ -154,8 +154,15 @@ namespace Artemis.Core.Services
? new DisplayConditionGroup(null, renderElement.RenderElementEntity.RootDisplayCondition) ? new DisplayConditionGroup(null, renderElement.RenderElementEntity.RootDisplayCondition)
: new DisplayConditionGroup(null); : new DisplayConditionGroup(null);
displayCondition.Initialize(_dataModelService); try
renderElement.DisplayConditionGroup = displayCondition; {
displayCondition.Initialize(_dataModelService);
renderElement.DisplayConditionGroup = displayCondition;
}
catch (Exception e)
{
_logger.Warning(e, $"Failed to init display conditions for {renderElement}");
}
} }
} }
} }

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile
{
public class DisplayConditionListEntity : DisplayConditionPartEntity
{
public DisplayConditionListEntity()
{
Children = new List<DisplayConditionPartEntity>();
}
public Guid? ListDataModelGuid { get; set; }
public string ListPropertyPath { get; set; }
public int ListOperator { get; set; }
}
}

View File

@ -1,19 +1,21 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile namespace Artemis.Storage.Entities.Profile
{ {
public class DisplayConditionListPredicateEntity : DisplayConditionPartEntity public class DisplayConditionListPredicateEntity : DisplayConditionPartEntity
{ {
public DisplayConditionListPredicateEntity() public int PredicateType { get; set; }
{
Children = new List<DisplayConditionPartEntity>();
}
public Guid? ListDataModelGuid { get; set; } public Guid? ListDataModelGuid { get; set; }
public string ListPropertyPath { get; set; } public string ListPropertyPath { get; set; }
public int ListOperator { get; set; } public string LeftPropertyPath { get; set; }
public string RightPropertyPath { get; set; }
// Stored as a string to be able to control serialization and deserialization ourselves
public string RightStaticValue { get; set; }
public string OperatorType { get; set; }
public Guid? OperatorPluginGuid { get; set; }
} }
} }

View File

@ -208,16 +208,15 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
var path = propertyPath.Split("."); var path = propertyPath.Split(".");
var currentPart = path.First(); var currentPart = path.First();
if (IsRootViewModel) if (IsRootViewModel)
{ {
var child = Children.FirstOrDefault(c => c.DataModel != null && var child = Children.FirstOrDefault(c => c.DataModel != null &&
c.DataModel.PluginInfo.Guid == dataModelGuid); c.DataModel.PluginInfo.Guid == dataModelGuid);
return child?.GetChildByPath(dataModelGuid, propertyPath); return child?.GetChildByPath(dataModelGuid, propertyPath);
} }
else else
{ {
var child = Children.FirstOrDefault(c => c.DataModel != null && var child = Children.FirstOrDefault(c => c.DataModel != null &&
c.DataModel.PluginInfo.Guid == dataModelGuid && c.PropertyInfo?.Name == currentPart); c.DataModel.PluginInfo.Guid == dataModelGuid && c.PropertyInfo?.Name == currentPart);
if (child == null) if (child == null)
return null; return null;

View File

@ -0,0 +1,53 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
namespace Artemis.UI.DataTemplateSelectors
{
// Source: https://stackoverflow.com/a/33421573/5015269
public class ComboBoxTemplateSelector : DataTemplateSelector
{
public DataTemplate SelectedItemTemplate { get; set; }
public DataTemplateSelector SelectedItemTemplateSelector { get; set; }
public DataTemplate DropdownItemsTemplate { get; set; }
public DataTemplateSelector DropdownItemsTemplateSelector { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or
// a ComboBoxItem (or null). This will determine which template to use
while (itemToCheck != null && !(itemToCheck is ComboBoxItem) && !(itemToCheck is ComboBox))
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown
var inDropDown = itemToCheck is ComboBoxItem;
return inDropDown
? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container)
: SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container);
}
}
public class ComboBoxTemplateSelectorExtension : MarkupExtension
{
public DataTemplate SelectedItemTemplate { get; set; }
public DataTemplateSelector SelectedItemTemplateSelector { get; set; }
public DataTemplate DropdownItemsTemplate { get; set; }
public DataTemplateSelector DropdownItemsTemplateSelector { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new ComboBoxTemplateSelector()
{
SelectedItemTemplate = SelectedItemTemplate,
SelectedItemTemplateSelector = SelectedItemTemplateSelector,
DropdownItemsTemplate = DropdownItemsTemplate,
DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
};
}
}
}

View File

@ -78,7 +78,7 @@ namespace Artemis.UI.Ninject.Factories
public interface IDisplayConditionsVmFactory : IVmFactory public interface IDisplayConditionsVmFactory : IVmFactory
{ {
DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent); DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent);
DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate, DisplayConditionViewModel parent); DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList, DisplayConditionViewModel parent);
DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent); DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent);
} }

View File

@ -6,17 +6,18 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput" xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core" xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}"> d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}">
<UserControl.Resources> <UserControl.Resources>
<ControlTemplate x:Key="SimpleTemplate"> <DataTemplate x:Key="SimpleTemplate">
<StackPanel d:DataContext="{d:DesignInstance {x:Type layerBrush:LayerBrushDescriptor}}" Orientation="Horizontal"> <StackPanel d:DataContext="{d:DesignInstance {x:Type layerBrush:LayerBrushDescriptor}}" Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding Icon}" Height="13" Width="13" Margin="0 1 3 0" /> <materialDesign:PackIcon Kind="{Binding Icon}" Height="13" Width="13" Margin="0 1 3 0" />
<TextBlock Text="{Binding DisplayName}" /> <TextBlock Text="{Binding DisplayName}" />
</StackPanel> </StackPanel>
</ControlTemplate> </DataTemplate>
<ControlTemplate x:Key="ExtendedTemplate"> <DataTemplate x:Key="ExtendedTemplate">
<Grid d:DataContext="{d:DesignInstance {x:Type layerBrush:LayerBrushDescriptor}}"> <Grid d:DataContext="{d:DesignInstance {x:Type layerBrush:LayerBrushDescriptor}}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@ -30,15 +31,8 @@
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding DisplayName}" TextWrapping="Wrap" MaxWidth="350" /> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding DisplayName}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" MaxWidth="350" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" /> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" MaxWidth="350" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
</Grid> </Grid>
</ControlTemplate>
<DataTemplate x:Key="DescriptorTemplate">
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate> </DataTemplate>
</UserControl.Resources> </UserControl.Resources>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputPrefix}" /> <TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputPrefix}" />
@ -51,7 +45,9 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
ItemsSource="{Binding Path=Descriptors}" ItemsSource="{Binding Path=Descriptors}"
SelectedValue="{Binding Path=SelectedDescriptor}" SelectedValue="{Binding Path=SelectedDescriptor}"
ItemTemplate="{StaticResource DescriptorTemplate}" /> ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource SimpleTemplate},
DropdownItemsTemplate={StaticResource ExtendedTemplate}}" />
<TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" /> <TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@ -71,7 +71,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
else if (type == "Dynamic") else if (type == "Dynamic")
DisplayConditionGroup.AddChild(new DisplayConditionPredicate(DisplayConditionGroup, PredicateType.Dynamic)); DisplayConditionGroup.AddChild(new DisplayConditionPredicate(DisplayConditionGroup, PredicateType.Dynamic));
else if (type == "List") else if (type == "List")
DisplayConditionGroup.AddChild(new DisplayConditionListPredicate(DisplayConditionGroup)); DisplayConditionGroup.AddChild(new DisplayConditionList(DisplayConditionGroup));
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -108,8 +108,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
case DisplayConditionGroup displayConditionGroup: case DisplayConditionGroup displayConditionGroup:
Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this)); Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this));
break; break;
case DisplayConditionListPredicate displayConditionListPredicate: case DisplayConditionList displayConditionListPredicate:
Children.Add(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate, this)); Children.Add(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate, this));
break; break;
case DisplayConditionPredicate displayConditionPredicate: case DisplayConditionPredicate displayConditionPredicate:
Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this)); Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this));

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionListPredicateView" <UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -10,7 +10,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:DisplayConditionListPredicateViewModel, IsDesignTimeCreatable=False}"> d:DataContext="{d:DesignInstance Type=local:DisplayConditionListViewModel, IsDesignTimeCreatable=False}">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View File

@ -4,11 +4,11 @@ using System.Windows.Controls;
namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
{ {
/// <summary> /// <summary>
/// Interaction logic for DisplayConditionListPredicateView.xaml /// Interaction logic for DisplayConditionListView.xaml
/// </summary> /// </summary>
public partial class DisplayConditionListPredicateView : UserControl public partial class DisplayConditionListView : UserControl
{ {
public DisplayConditionListPredicateView() public DisplayConditionListView()
{ {
InitializeComponent(); InitializeComponent();
} }

View File

@ -17,7 +17,7 @@ using Humanizer;
namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
{ {
public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel public class DisplayConditionListViewModel : DisplayConditionViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IDataModelVisualizationService _dataModelVisualizationService; private readonly IDataModelVisualizationService _dataModelVisualizationService;
@ -27,13 +27,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
private DataModelPropertiesViewModel _targetDataModel; private DataModelPropertiesViewModel _targetDataModel;
private readonly Timer _updateTimer; private readonly Timer _updateTimer;
public DisplayConditionListPredicateViewModel( public DisplayConditionListViewModel(
DisplayConditionListPredicate displayConditionListPredicate, DisplayConditionList displayConditionList,
DisplayConditionViewModel parent, DisplayConditionViewModel parent,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IDataModelVisualizationService dataModelVisualizationService, IDataModelVisualizationService dataModelVisualizationService,
IDisplayConditionsVmFactory displayConditionsVmFactory, IDisplayConditionsVmFactory displayConditionsVmFactory,
ISettingsService settingsService) : base(displayConditionListPredicate, parent) ISettingsService settingsService) : base(displayConditionList, parent)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_dataModelVisualizationService = dataModelVisualizationService; _dataModelVisualizationService = dataModelVisualizationService;
@ -51,7 +51,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public DelegateCommand SelectListPropertyCommand { get; } public DelegateCommand SelectListPropertyCommand { get; }
public PluginSetting<bool> ShowDataModelValues { get; } public PluginSetting<bool> ShowDataModelValues { get; }
public DisplayConditionListPredicate DisplayConditionListPredicate => (DisplayConditionListPredicate) Model; public DisplayConditionList DisplayConditionList => (DisplayConditionList) Model;
public bool IsInitialized public bool IsInitialized
{ {
@ -73,21 +73,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
set => SetAndNotify(ref _selectedListProperty, value); set => SetAndNotify(ref _selectedListProperty, value);
} }
public string SelectedListOperator => DisplayConditionListPredicate.ListOperator.Humanize(); public string SelectedListOperator => DisplayConditionList.ListOperator.Humanize();
public void SelectListOperator(string type) public void SelectListOperator(string type)
{ {
var enumValue = Enum.Parse<ListOperator>(type); var enumValue = Enum.Parse<ListOperator>(type);
DisplayConditionListPredicate.ListOperator = enumValue; DisplayConditionList.ListOperator = enumValue;
NotifyOfPropertyChange(nameof(SelectedListOperator)); NotifyOfPropertyChange(nameof(SelectedListOperator));
} }
public void AddCondition(string type) public void AddCondition(string type)
{ {
if (type == "Static") if (type == "Static")
DisplayConditionListPredicate.AddChild(new DisplayConditionPredicate(DisplayConditionListPredicate, PredicateType.Static)); DisplayConditionList.AddChild(new DisplayConditionPredicate(DisplayConditionList, PredicateType.Static));
else if (type == "Dynamic") else if (type == "Dynamic")
DisplayConditionListPredicate.AddChild(new DisplayConditionPredicate(DisplayConditionListPredicate, PredicateType.Dynamic)); DisplayConditionList.AddChild(new DisplayConditionPredicate(DisplayConditionList, PredicateType.Dynamic));
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -95,7 +95,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public void AddGroup() public void AddGroup()
{ {
DisplayConditionListPredicate.AddChild(new DisplayConditionGroup(DisplayConditionListPredicate)); DisplayConditionList.AddChild(new DisplayConditionGroup(DisplayConditionList));
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -146,7 +146,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public void ApplyList() public void ApplyList()
{ {
DisplayConditionListPredicate.UpdateList(SelectedListProperty.DataModel, SelectedListProperty.PropertyPath); DisplayConditionList.UpdateList(SelectedListProperty.DataModel, SelectedListProperty.PropertyPath);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
Update(); Update();
@ -155,7 +155,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public override DataModelPropertiesViewModel GetDataModelOverride() public override DataModelPropertiesViewModel GetDataModelOverride()
{ {
if (SelectedListProperty != null) if (SelectedListProperty != null)
return (DataModelPropertiesViewModel) SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService); return SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService);
return base.GetDataModelOverride(); return base.GetDataModelOverride();
} }
@ -168,9 +168,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
NotifyOfPropertyChange(nameof(SelectedListOperator)); NotifyOfPropertyChange(nameof(SelectedListOperator));
// Update the selected list property // Update the selected list property
if (DisplayConditionListPredicate.ListDataModel != null && DisplayConditionListPredicate.ListPropertyPath != null) if (DisplayConditionList.ListDataModel != null && DisplayConditionList.ListPropertyPath != null)
{ {
var child = TargetDataModel.GetChildByPath(DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid, DisplayConditionListPredicate.ListPropertyPath); var child = TargetDataModel.GetChildByPath(
DisplayConditionList.ListDataModel.PluginInfo.Guid,
DisplayConditionList.ListPropertyPath
);
SelectedListProperty = child as DataModelListViewModel; SelectedListProperty = child as DataModelListViewModel;
} }
@ -178,7 +181,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
TargetDataModel.ApplyTypeFilter(true, typeof(IList)); TargetDataModel.ApplyTypeFilter(true, typeof(IList));
// Remove VMs of effects no longer applied on the layer // Remove VMs of effects no longer applied on the layer
var toRemove = Children.Where(c => !DisplayConditionListPredicate.Children.Contains(c.Model)).ToList(); var toRemove = Children.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList();
// Using RemoveRange breaks our lovely animations // Using RemoveRange breaks our lovely animations
foreach (var displayConditionViewModel in toRemove) foreach (var displayConditionViewModel in toRemove)
{ {

View File

@ -8,12 +8,10 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:utilities="clr-namespace:Artemis.UI.Utilities" xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared"
x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionPredicateView" x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionPredicateView"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionPredicateViewModel}}" d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionPredicateViewModel}}">
x:Name="DisplayConditionPredicateRoot">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View File

@ -9,6 +9,7 @@
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
xmlns:profile="clr-namespace:Artemis.Core.Models.Profile;assembly=Artemis.Core" xmlns:profile="clr-namespace:Artemis.Core.Models.Profile;assembly=Artemis.Core"
xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core" xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
mc:Ignorable="d" mc:Ignorable="d"
behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True" behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
@ -22,12 +23,12 @@
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" /> Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<ControlTemplate x:Key="SimpleTemplate"> <DataTemplate x:Key="SimpleTemplate">
<StackPanel d:DataContext="{d:DesignInstance {x:Type profile:ProfileDescriptor}}" Orientation="Horizontal"> <StackPanel d:DataContext="{d:DesignInstance {x:Type profile:ProfileDescriptor}}" Orientation="Horizontal">
<TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Name}" />
</StackPanel> </StackPanel>
</ControlTemplate> </DataTemplate>
<ControlTemplate x:Key="ExtendedTemplate"> <DataTemplate x:Key="ExtendedTemplate">
<Grid d:DataContext="{d:DesignInstance {x:Type profile:ProfileDescriptor}}"> <Grid d:DataContext="{d:DesignInstance {x:Type profile:ProfileDescriptor}}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -44,14 +45,6 @@
<materialDesign:PackIcon Kind="TrashCanOutline" Height="14" Width="14" HorizontalAlignment="Right" /> <materialDesign:PackIcon Kind="TrashCanOutline" Height="14" Width="14" HorizontalAlignment="Right" />
</Button> </Button>
</Grid> </Grid>
</ControlTemplate>
<DataTemplate x:Key="ProfileDescriptorTemplate">
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate> </DataTemplate>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
@ -138,7 +131,9 @@
VerticalAlignment="Top" VerticalAlignment="Top"
ItemsSource="{Binding Profiles}" ItemsSource="{Binding Profiles}"
SelectedItem="{Binding SelectedProfile}" SelectedItem="{Binding SelectedProfile}"
ItemTemplate="{StaticResource ProfileDescriptorTemplate}"> ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource SimpleTemplate},
DropdownItemsTemplate={StaticResource ExtendedTemplate}}" >
<ComboBox.ItemsPanel> <ComboBox.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<VirtualizingStackPanel /> <VirtualizingStackPanel />