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

Display conditions - Finished list conditions

This commit is contained in:
SpoinkyNL 2020-08-16 16:14:48 +02:00
parent 75e2fb1689
commit 12456519d5
15 changed files with 702 additions and 85 deletions

View File

@ -17,9 +17,6 @@ namespace Artemis.Core.Models.Profile.Conditions
{
Parent = parent;
Entity = new DisplayConditionListEntity();
// There is always a child root group, add it
AddChild(new DisplayConditionGroup(this));
}
public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity)
@ -27,16 +24,6 @@ namespace Artemis.Core.Models.Profile.Conditions
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; }
@ -55,6 +42,8 @@ namespace Artemis.Core.Models.Profile.Conditions
public override bool EvaluateObject(object target)
{
if (!Children.Any())
return false;
if (!(target is IList list))
return false;
@ -92,17 +81,28 @@ namespace Artemis.Core.Models.Profile.Conditions
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);
}
if (Entity.ListDataModelGuid == null)
return;
// Children
var rootGroup = (DisplayConditionGroup) Children.Single();
rootGroup.Initialize(dataModelService);
// Get the data model and ensure the path is valid
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value);
if (dataModel == null || !dataModel.ContainsPath(Entity.ListPropertyPath))
return;
// Populate properties and create the accessor expression
ListDataModel = dataModel;
ListPropertyPath = Entity.ListPropertyPath;
CreateExpression();
// There should only be one child and it should be a group
if (Entity.Children.SingleOrDefault() is DisplayConditionGroupEntity rootGroup)
AddChild(new DisplayConditionGroup(this, rootGroup));
else
{
Entity.Children.Clear();
AddChild(new DisplayConditionGroup(this));
}
Children[0].Initialize(dataModelService);
}
public void UpdateList(DataModel dataModel, string path)
@ -120,20 +120,31 @@ namespace Artemis.Core.Models.Profile.Conditions
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
}
// Remove the old root group that was tied to the old data model
while (Children.Any())
RemoveChild(Children[0]);
ListDataModel = dataModel;
ListPropertyPath = path;
if (dataModel != null)
if (dataModel == null)
return;
// Create a new root group
AddChild(new DisplayConditionGroup(this));
CreateExpression();
}
public void CreateExpression()
{
var parameter = Expression.Parameter(typeof(object), "listDataModel");
var accessor = path.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, dataModel.GetType()),
var accessor = ListPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, ListDataModel.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; }
}

View File

@ -19,6 +19,8 @@ namespace Artemis.Core.Models.Profile.Conditions
Parent = parent;
PredicateType = predicateType;
Entity = new DisplayConditionListPredicateEntity();
ApplyParentList();
}
public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity)
@ -26,6 +28,8 @@ namespace Artemis.Core.Models.Profile.Conditions
Parent = parent;
Entity = entity;
PredicateType = (PredicateType) entity.PredicateType;
ApplyParentList();
}
public DisplayConditionListPredicateEntity Entity { get; set; }
@ -43,6 +47,20 @@ namespace Artemis.Core.Models.Profile.Conditions
public Func<object, bool> CompiledListPredicate { get; private set; }
public void ApplyParentList()
{
var current = Parent;
while (current != null)
{
if (current is DisplayConditionList parentList)
{
UpdateList(parentList.ListDataModel, parentList.ListPropertyPath);
return;
}
current = current.Parent;
}
}
public void UpdateList(DataModel dataModel, string path)
{
if (dataModel != null && path == null)
@ -66,9 +84,9 @@ namespace Artemis.Core.Models.Profile.Conditions
ListDataModel = dataModel;
ListPropertyPath = path;
if (!ListContainsInnerPath(LeftPropertyPath))
if (LeftPropertyPath != null && !ListContainsInnerPath(LeftPropertyPath))
LeftPropertyPath = null;
if (!ListContainsInnerPath(RightPropertyPath))
if (RightPropertyPath != null && !ListContainsInnerPath(RightPropertyPath))
RightPropertyPath = null;
CreateExpression();

View File

@ -34,9 +34,9 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
set => SetAndNotify(ref _displayValue, value);
}
public override string PropertyPath => Parent?.PropertyPath;
public override string PropertyPath => null;
public override string DisplayPropertyPath => Parent?.DisplayPropertyPath;
public override string DisplayPropertyPath => null;
public override void Update(IDataModelVisualizationService dataModelVisualizationService)
{

View File

@ -198,6 +198,9 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
public DataModelVisualizationViewModel GetChildByPath(Guid dataModelGuid, string propertyPath)
{
if (propertyPath == null)
return null;
// Ensure children are populated by requesting an update
if (!IsVisualizationExpanded)
{

View File

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

View File

@ -1,6 +1,5 @@
using System;
using Artemis.Core.Models.Profile.Conditions.Abstract;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract
@ -29,11 +28,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract
public abstract void Update();
public virtual DataModelPropertiesViewModel GetDataModelOverride()
{
return Parent?.GetDataModelOverride();
}
public virtual void Delete()
{
Model.Parent.RemoveChild(Model);

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionGroupView"
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,14 +6,18 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:Converters="clr-namespace:Artemis.UI.Converters"
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionGroupView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:DisplayConditionGroupViewModel, IsDesignTimeCreatable=False}">
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionGroupViewModel}}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DisplayConditions.xaml" />
</ResourceDictionary.MergedDictionaries>
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
</ResourceDictionary>
</UserControl.Resources>
@ -68,8 +72,7 @@
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignIconForegroundButton}">
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<EventTrigger RoutedEvent="ButtonBase.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
@ -77,14 +80,15 @@
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<materialDesign:PackIcon Kind="Add" Width="18" Height="18" />
<Button.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</ContextMenu.Resources>
<MenuItem Header="Add static condition"
ToolTip="A condition that compares a data model property to a static input"
Command="{s:Action AddCondition}"
@ -104,7 +108,8 @@
<MenuItem Header="Add list condition"
ToolTip="A condition that evaluates on items in a list"
Command="{s:Action AddCondition}"
CommandParameter="List">
CommandParameter="List"
IsEnabled="{Binding Data.IsListGroup, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="FormatListBulleted" />
</MenuItem.Icon>
@ -116,6 +121,7 @@
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
<materialDesign:PackIcon Kind="Add" Width="18" Height="18" />
</Button>
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource="{Binding Children}">

View File

@ -18,9 +18,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
private bool _isInitialized;
private bool _isRootGroup;
public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent,
public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent, bool isListGroup,
IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) : base(displayConditionGroup, parent)
{
IsListGroup = isListGroup;
_profileEditorService = profileEditorService;
_displayConditionsVmFactory = displayConditionsVmFactory;
@ -33,6 +34,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
});
}
public bool IsListGroup { get; }
public DisplayConditionGroup DisplayConditionGroup => (DisplayConditionGroup) Model;
public bool IsRootGroup
@ -62,15 +65,27 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
var enumValue = Enum.Parse<BooleanOperator>(type);
DisplayConditionGroup.BooleanOperator = enumValue;
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
_profileEditorService.UpdateSelectedProfileElement();
}
public void AddCondition(string type)
{
if (type == "Static")
{
if (!IsListGroup)
DisplayConditionGroup.AddChild(new DisplayConditionPredicate(DisplayConditionGroup, PredicateType.Static));
else
DisplayConditionGroup.AddChild(new DisplayConditionListPredicate(DisplayConditionGroup, PredicateType.Static));
}
else if (type == "Dynamic")
{
if (!IsListGroup)
DisplayConditionGroup.AddChild(new DisplayConditionPredicate(DisplayConditionGroup, PredicateType.Dynamic));
else if (type == "List")
else
DisplayConditionGroup.AddChild(new DisplayConditionListPredicate(DisplayConditionGroup, PredicateType.Dynamic));
}
else if (type == "List" && !IsListGroup)
DisplayConditionGroup.AddChild(new DisplayConditionList(DisplayConditionGroup));
Update();
@ -106,14 +121,19 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
switch (childModel)
{
case DisplayConditionGroup displayConditionGroup:
Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this));
Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this, IsListGroup));
break;
case DisplayConditionList displayConditionListPredicate:
Children.Add(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate, this));
break;
case DisplayConditionPredicate displayConditionPredicate:
if (!IsListGroup)
Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this));
break;
case DisplayConditionListPredicate displayConditionListPredicate:
if (IsListGroup)
Children.Add(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate, this));
break;
}
}

View File

@ -0,0 +1,187 @@
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionListPredicateView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type displayConditions:DisplayConditionListPredicateViewModel}}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DisplayConditions.xaml" />
<ResourceDictionary>
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
<DataTemplate x:Key="DataModelDataTemplate">
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource DataModelSelectionTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Data.ShowDataModelValues.Value, Source={StaticResource DataContextProxy}}" Value="True">
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource DataModelSelectionTemplateWithValues}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="0 3" Visibility="{Binding IsInitialized, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0"
Grid.Column="0"
ToolTip="Delete the predicate"
Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Left"
Foreground="#E74C4C"
Width="25"
Height="25"
Command="{s:Action Delete}">
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
</Button>
<!-- Left side, always a property -->
<Button Grid.Row="0"
Grid.Column="1"
Background="#476CBC"
BorderBrush="#476CBC"
Style="{StaticResource DisplayConditionButton}"
ToolTip="{Binding SelectedLeftSideProperty.DisplayPropertyPath}"
Click="PropertyButton_OnClick">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding LeftSideDataModel.Children}" IsOpen="{Binding LeftSideDataModelOpen, Mode=OneWayToSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="ItemsSource" Value="{Binding Children}" />
<Setter Property="Command" Value="{Binding Data.SelectLeftPropertyCommand, Source={StaticResource DataContextProxy}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="CommandTarget" Value="{Binding}" />
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
<Setter Property="HeaderTemplate" Value="{StaticResource DataModelDataTemplate}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
<Grid>
<TextBlock Text="{Binding SelectedLeftSideProperty.PropertyDescription.Name}"
Visibility="{Binding SelectedLeftSideProperty, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock Text="« Select a property »"
FontStyle="Italic"
Visibility="{Binding SelectedLeftSideProperty, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}" />
</Grid>
</Button>
<!-- Operator -->
<Button Grid.Row="0"
Grid.Column="2"
Style="{StaticResource DisplayConditionButtonLeftClickMenu}"
Background="#7B7B7B"
BorderBrush="#7B7B7B"
Content="{Binding SelectedOperator.Description}"
Click="PropertyButton_OnClick">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Operators}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding Icon}" VerticalAlignment="Center" Margin="0 0 15 0" />
<TextBlock Text="{Binding Description}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="Command" Value="{Binding Data.SelectOperatorCommand, Source={StaticResource DataContextProxy}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="CommandTarget" Value="{Binding}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
<Grid Grid.Row="0"
Grid.Column="3"
Visibility="{Binding SelectedOperator.SupportsRightSide, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<!-- Right side property if type is dynamic -->
<Button Background="{DynamicResource PrimaryHueMidBrush}"
BorderBrush="{DynamicResource PrimaryHueMidBrush}"
Style="{StaticResource DisplayConditionButton}"
ToolTip="{Binding SelectedRightSideProperty.DisplayPropertyPath}"
Click="PropertyButton_OnClick"
Visibility="{Binding ShowRightSidePropertySelection, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding RightSideDataModel.Children}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="ItemsSource" Value="{Binding Children}" />
<Setter Property="Command" Value="{Binding Data.SelectRightPropertyCommand, Source={StaticResource DataContextProxy}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="CommandTarget" Value="{Binding}" />
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
<Setter Property="HeaderTemplate" Value="{StaticResource DataModelDataTemplate}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
<Grid>
<TextBlock Text="{Binding SelectedRightSideProperty.PropertyDescription.Name}"
Visibility="{Binding SelectedRightSideProperty, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock Text="« Select a property »"
FontStyle="Italic"
Visibility="{Binding SelectedRightSideProperty, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}" />
</Grid>
</Button>
<!-- Right side property if type is static -->
<materialDesign:Transitioner SelectedIndex="{Binding RightSideTransitionIndex}"
DefaultTransitionOrigin="0.5, 0.5"
Margin="3 -4"
Visibility="{Binding ShowRightSidePropertySelection, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<Button Style="{StaticResource DisplayConditionButton}"
Background="{DynamicResource PrimaryHueMidBrush}"
BorderBrush="{DynamicResource PrimaryHueMidBrush}"
Margin="0 4"
Command="{s:Action ActivateRightSideInputViewModel}"
HorizontalAlignment="Left">
<Grid>
<StackPanel Visibility="{Binding RightStaticValue, Converter={StaticResource NullToVisibilityConverter}}" Orientation="Horizontal">
<TextBlock FontWeight="Light"
Text="{Binding SelectedLeftSideProperty.PropertyDescription.Prefix}"
Visibility="{Binding SelectedLeftSideProperty.PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock Text="{Binding RightStaticValue}" />
<TextBlock FontWeight="Light"
Text="{Binding SelectedLeftSideProperty.PropertyDescription.Affix}"
Visibility="{Binding SelectedLeftSideProperty.PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}" />
</StackPanel>
<TextBlock Text="« Enter a value »"
FontStyle="Italic"
Visibility="{Binding RightStaticValue, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}" />
</Grid>
</Button>
<Border BorderBrush="{DynamicResource PrimaryHueMidBrush}"
Background="{DynamicResource MaterialDesignPaper}"
CornerRadius="3"
Padding="3"
HorizontalAlignment="Left"
MinWidth="140">
<ContentControl s:View.Model="{Binding RightSideInputViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</Border>
</materialDesign:Transitioner>
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,26 @@
using System.Windows;
using System.Windows.Controls;
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
{
/// <summary>
/// Interaction logic for DisplayConditionListPredicateView.xaml
/// </summary>
public partial class DisplayConditionListPredicateView : UserControl
{
public DisplayConditionListPredicateView()
{
InitializeComponent();
}
private void PropertyButton_OnClick(object sender, RoutedEventArgs e)
{
// DataContext is not set when using left button, I don't know why but there it is
if (sender is Button button && button.ContextMenu != null)
{
button.ContextMenu.DataContext = button.DataContext;
button.ContextMenu.IsOpen = true;
}
}
}
}

View File

@ -0,0 +1,361 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Events;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract;
using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Utilities;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
{
public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel, IHandle<MainWindowKeyEvent>, IHandle<MainWindowMouseEvent>
{
private readonly IDataModelService _dataModelService;
private readonly IDataModelVisualizationService _dataModelVisualizationService;
private readonly IEventAggregator _eventAggregator;
private readonly IProfileEditorService _profileEditorService;
private readonly Timer _updateTimer;
private bool _isInitialized;
private DataModelVisualizationViewModel _leftSideDataModel;
private BindableCollection<DisplayConditionOperator> _operators;
private DataModelVisualizationViewModel _rightSideDataModel;
private DataModelInputViewModel _rightSideInputViewModel;
private int _rightSideTransitionIndex;
private object _rightStaticValue;
private DataModelVisualizationViewModel _selectedLeftSideProperty;
private DisplayConditionOperator _selectedOperator;
private DataModelVisualizationViewModel _selectedRightSideProperty;
private List<Type> _supportedInputTypes;
public DisplayConditionListPredicateViewModel(
DisplayConditionListPredicate displayConditionListPredicate,
DisplayConditionViewModel parent,
IProfileEditorService profileEditorService,
IDataModelVisualizationService dataModelVisualizationService,
IDataModelService dataModelService,
ISettingsService settingsService,
IEventAggregator eventAggregator) : base(displayConditionListPredicate, parent)
{
_profileEditorService = profileEditorService;
_dataModelVisualizationService = dataModelVisualizationService;
_dataModelService = dataModelService;
_eventAggregator = eventAggregator;
_updateTimer = new Timer(500);
_supportedInputTypes = new List<Type>();
SelectLeftPropertyCommand = new DelegateCommand(ExecuteSelectLeftProperty);
SelectRightPropertyCommand = new DelegateCommand(ExecuteSelectRightProperty);
SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand);
Operators = new BindableCollection<DisplayConditionOperator>();
ShowDataModelValues = settingsService.GetSetting<bool>("ProfileEditor.ShowDataModelValues");
// Initialize async, no need to wait for it
Task.Run(Initialize);
}
public DisplayConditionListPredicate DisplayConditionListPredicate => (DisplayConditionListPredicate) Model;
public bool ShowRightSidePropertySelection => DisplayConditionListPredicate.PredicateType == PredicateType.Dynamic;
public bool CanActivateRightSideInputViewModel => SelectedLeftSideProperty?.PropertyInfo != null;
public PluginSetting<bool> ShowDataModelValues { get; }
public bool IsInitialized
{
get => _isInitialized;
private set => SetAndNotify(ref _isInitialized, value);
}
public bool LeftSideDataModelOpen { get; set; }
public DataModelVisualizationViewModel LeftSideDataModel
{
get => _leftSideDataModel;
set => SetAndNotify(ref _leftSideDataModel, value);
}
public DataModelVisualizationViewModel RightSideDataModel
{
get => _rightSideDataModel;
set => SetAndNotify(ref _rightSideDataModel, value);
}
public bool RightSideDataModelOpen { get; set; }
public DataModelVisualizationViewModel SelectedLeftSideProperty
{
get => _selectedLeftSideProperty;
set
{
if (!SetAndNotify(ref _selectedLeftSideProperty, value)) return;
NotifyOfPropertyChange(nameof(CanActivateRightSideInputViewModel));
}
}
public DataModelVisualizationViewModel SelectedRightSideProperty
{
get => _selectedRightSideProperty;
set => SetAndNotify(ref _selectedRightSideProperty, value);
}
public object RightStaticValue
{
get => _rightStaticValue;
set => SetAndNotify(ref _rightStaticValue, value);
}
public int RightSideTransitionIndex
{
get => _rightSideTransitionIndex;
set => SetAndNotify(ref _rightSideTransitionIndex, value);
}
public DataModelInputViewModel RightSideInputViewModel
{
get => _rightSideInputViewModel;
set => SetAndNotify(ref _rightSideInputViewModel, value);
}
public BindableCollection<DisplayConditionOperator> Operators
{
get => _operators;
set => SetAndNotify(ref _operators, value);
}
public DisplayConditionOperator SelectedOperator
{
get => _selectedOperator;
set => SetAndNotify(ref _selectedOperator, value);
}
public DelegateCommand SelectLeftPropertyCommand { get; }
public DelegateCommand SelectRightPropertyCommand { get; }
public DelegateCommand SelectOperatorCommand { get; }
public void Handle(MainWindowKeyEvent message)
{
if (RightSideInputViewModel == null)
return;
if (!message.KeyDown && message.EventArgs.Key == Key.Escape)
RightSideInputViewModel.Cancel();
if (!message.KeyDown && message.EventArgs.Key == Key.Enter)
RightSideInputViewModel.Submit();
}
public void Handle(MainWindowMouseEvent message)
{
if (RightSideInputViewModel == null)
return;
if (message.Sender is FrameworkElement frameworkElement && !frameworkElement.IsDescendantOf(RightSideInputViewModel.View))
RightSideInputViewModel.Submit();
}
public override void Delete()
{
base.Delete();
_profileEditorService.UpdateSelectedProfileElement();
}
public void Initialize()
{
// Get the data models
LeftSideDataModel = GetListDataModel();
RightSideDataModel = GetListDataModel();
LeftSideDataModel.UpdateRequested += LeftDataModelUpdateRequested;
RightSideDataModel.UpdateRequested += RightDataModelUpdateRequested;
// Determine which types are currently supported
var editors = _dataModelVisualizationService.RegisteredDataModelEditors;
_supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
_supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
Update();
_updateTimer.Start();
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
IsInitialized = true;
}
public override void Update()
{
// Not yet initialized if these are null
if (LeftSideDataModel == null || RightSideDataModel == null)
return;
var listDataModelGuid = DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid;
// If static, only allow selecting properties also supported by input
if (DisplayConditionListPredicate.PredicateType == PredicateType.Static)
LeftSideDataModel.ApplyTypeFilter(false, _supportedInputTypes.ToArray());
// Determine the left side property first
SelectedLeftSideProperty = LeftSideDataModel.GetChildByPath(listDataModelGuid, DisplayConditionListPredicate.LeftPropertyPath);
var leftSideType = SelectedLeftSideProperty?.PropertyInfo?.PropertyType;
// Get the supported operators
Operators.Clear();
Operators.AddRange(_dataModelService.GetCompatibleConditionOperators(leftSideType));
if (DisplayConditionListPredicate.Operator == null)
DisplayConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType)));
SelectedOperator = DisplayConditionListPredicate.Operator;
// Determine the right side
if (DisplayConditionListPredicate.PredicateType == PredicateType.Dynamic)
{
SelectedRightSideProperty = RightSideDataModel.GetChildByPath(listDataModelGuid, DisplayConditionListPredicate.RightPropertyPath);
RightSideDataModel.ApplyTypeFilter(true, leftSideType);
}
else
RightStaticValue = DisplayConditionListPredicate.RightStaticValue;
}
public void ApplyLeftSide()
{
DisplayConditionListPredicate.UpdateLeftSide(SelectedLeftSideProperty.PropertyPath);
_profileEditorService.UpdateSelectedProfileElement();
SelectedOperator = DisplayConditionListPredicate.Operator;
Update();
}
public void ApplyRightSideDynamic()
{
DisplayConditionListPredicate.UpdateRightSideDynamic(SelectedRightSideProperty.PropertyPath);
_profileEditorService.UpdateSelectedProfileElement();
Update();
}
public void ApplyRightSideStatic(object value, bool isSubmitted)
{
if (isSubmitted)
{
DisplayConditionListPredicate.UpdateRightSideStatic(value);
_profileEditorService.UpdateSelectedProfileElement();
Update();
}
RightSideTransitionIndex = 0;
RightSideInputViewModel = null;
RightStaticValue = value;
_eventAggregator.Unsubscribe(this);
}
public void ApplyOperator()
{
DisplayConditionListPredicate.UpdateOperator(SelectedOperator);
_profileEditorService.UpdateSelectedProfileElement();
Update();
}
public void ActivateRightSideInputViewModel()
{
if (SelectedLeftSideProperty?.PropertyInfo == null)
return;
RightSideTransitionIndex = 1;
RightSideInputViewModel = _dataModelVisualizationService.GetDataModelInputViewModel(
SelectedLeftSideProperty.PropertyInfo.PropertyType,
SelectedLeftSideProperty.PropertyDescription,
DisplayConditionListPredicate.RightStaticValue,
ApplyRightSideStatic
);
_eventAggregator.Subscribe(this);
}
protected override void Dispose(bool disposing)
{
_updateTimer.Stop();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
}
private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
{
if (LeftSideDataModelOpen)
LeftSideDataModel.Update(_dataModelVisualizationService);
else if (RightSideDataModelOpen)
RightSideDataModel.Update(_dataModelVisualizationService);
}
private void RightDataModelUpdateRequested(object sender, EventArgs e)
{
var listDataModelGuid = DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid;
var leftSideType = SelectedLeftSideProperty?.PropertyInfo?.PropertyType;
// If the right side property is missing it may be available now that the data model has been updated
if (SelectedRightSideProperty == null && DisplayConditionListPredicate.RightPropertyPath != null)
SelectedRightSideProperty = RightSideDataModel.GetChildByPath(listDataModelGuid, DisplayConditionListPredicate.RightPropertyPath);
// With the data model updated, also reapply the filter
RightSideDataModel.ApplyTypeFilter(true, leftSideType);
}
private void LeftDataModelUpdateRequested(object sender, EventArgs e)
{
if (DisplayConditionListPredicate.PredicateType == PredicateType.Static)
LeftSideDataModel.ApplyTypeFilter(false, _supportedInputTypes.ToArray());
}
private DataModelVisualizationViewModel GetListDataModel()
{
if (DisplayConditionListPredicate.ListDataModel == null || DisplayConditionListPredicate.ListPropertyPath == null)
throw new ArtemisUIException("Cannot create a list predicate without first selecting a target list");
var dataModel = _dataModelVisualizationService.GetMainDataModelVisualization();
if (!_dataModelVisualizationService.GetPluginExtendsDataModel(_profileEditorService.GetCurrentModule()))
dataModel.Children.Add(_dataModelVisualizationService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule()));
var listDataModel = (DataModelListViewModel) dataModel.GetChildByPath(
DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid,
DisplayConditionListPredicate.ListPropertyPath
);
return listDataModel.GetListTypeViewModel(_dataModelVisualizationService);
}
private void ExecuteSelectLeftProperty(object context)
{
if (!(context is DataModelVisualizationViewModel dataModelVisualizationViewModel))
return;
SelectedLeftSideProperty = dataModelVisualizationViewModel;
ApplyLeftSide();
}
private void ExecuteSelectRightProperty(object context)
{
if (!(context is DataModelVisualizationViewModel dataModelVisualizationViewModel))
return;
SelectedRightSideProperty = dataModelVisualizationViewModel;
ApplyRightSideDynamic();
}
private void ExecuteSelectOperatorCommand(object context)
{
if (!(context is DisplayConditionOperator displayConditionOperator))
return;
SelectedOperator = displayConditionOperator;
ApplyOperator();
}
}
}

View File

@ -30,7 +30,7 @@
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="0 3" Visibility="{Binding IsInitialized, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Grid Margin="0 3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@ -55,11 +55,12 @@
<!-- Left side, the list this predicate is targeting -->
<Button Grid.Row="0"
Grid.Column="1"
Background="#ab47bc"
BorderBrush="#ab47bc"
Background="#476CBC"
BorderBrush="#476CBC"
Style="{StaticResource DisplayConditionButton}"
ToolTip="{Binding SelectedListProperty.DisplayPropertyPath}"
Click="PropertyButton_OnClick">
Click="PropertyButton_OnClick"
Visibility="{Binding IsInitialized, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding TargetDataModel.Children}" IsOpen="{Binding TargetDataModelOpen, Mode=OneWayToSource}">
<ContextMenu.ItemContainerStyle>
@ -92,7 +93,8 @@
BorderBrush="#7B7B7B"
Content="{Binding SelectedListOperator}"
Click="PropertyButton_OnClick"
HorizontalAlignment="Left">
HorizontalAlignment="Left"
Visibility="{Binding IsInitialized, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Any" Command="{s:Action SelectListOperator}" CommandParameter="Any" />

View File

@ -79,6 +79,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
var enumValue = Enum.Parse<ListOperator>(type);
DisplayConditionList.ListOperator = enumValue;
NotifyOfPropertyChange(nameof(SelectedListOperator));
_profileEditorService.UpdateSelectedProfileElement();
}
public void AddCondition(string type)
@ -151,14 +153,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
Update();
}
public override DataModelPropertiesViewModel GetDataModelOverride()
{
if (SelectedListProperty != null)
return SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService);
return base.GetDataModelOverride();
}
public override void Update()
{
if (TargetDataModel == null)
@ -195,7 +189,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
if (!(childModel is DisplayConditionGroup displayConditionGroup))
continue;
var viewModel = _displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this);
var viewModel = _displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this, true);
viewModel.IsRootGroup = true;
Children.Add(viewModel);
}

View File

@ -202,9 +202,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
if (LeftSideDataModel == null || DisplayConditionPredicate.PredicateType == PredicateType.Dynamic && RightSideDataModel == null)
return;
if (GetDataModelOverride() != null)
LeftSideDataModel = GetDataModelOverride();
// If static, only allow selecting properties also supported by input
if (DisplayConditionPredicate.PredicateType == PredicateType.Static)
LeftSideDataModel.ApplyTypeFilter(false, _supportedInputTypes.ToArray());
@ -224,9 +221,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
if (DisplayConditionPredicate.PredicateType == PredicateType.Dynamic)
{
SelectedRightSideProperty = LeftSideDataModel.GetChildForCondition(DisplayConditionPredicate, DisplayConditionSide.Right);
if (GetDataModelOverride() != null)
RightSideDataModel = GetDataModelOverride();
RightSideDataModel.ApplyTypeFilter(true, leftSideType);
}
else

View File

@ -98,7 +98,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
e.RenderProfileElement.DisplayConditionGroup = new DisplayConditionGroup(null);
RootGroup?.Dispose();
RootGroup = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, null);
RootGroup = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, null, false);
RootGroup.IsRootGroup = true;
RootGroup.Update();