Profile modules - Encapsulation fixes

Data bindings - Abstracted the data model selection
This commit is contained in:
SpoinkyNL committed 2020-09-04 20:24:34 +02:00
1 parent 76dcbaf6d7
commit d03ca9c84a
20 files changed
+502 -189

No files matched your search

@@ -0,0 +1,41 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Artemis.UI.Shared
{
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Parameters direction;
if (parameter == null)
direction = Parameters.Normal;
else
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
if (direction == Parameters.Normal)
{
if (value == null)
return Visibility.Collapsed;
return Visibility.Visible;
}
if (value == null)
return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private enum Parameters
{
Normal,
Inverted
}
}
}
@@ -0,0 +1,60 @@
<UserControl x:Class="Artemis.UI.Shared.DataModelSelectionView"
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:shared="clr-namespace:Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance shared:DataModelSelectionViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DisplayConditions.xaml" />
<ResourceDictionary>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<shared: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>
<Button Background="{Binding ButtonBrush}"
BorderBrush="{Binding ButtonBrush}"
Style="{StaticResource DisplayConditionButton}"
ToolTip="{Binding SelectedPropertyViewModel.DisplayPropertyPath}"
IsEnabled="{Binding IsEnabled}"
HorizontalAlignment="Left"
Click="PropertyButton_OnClick">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding DataModelViewModel.Children}" IsOpen="{Binding IsDataModelViewModelOpen, Mode=OneWayToSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="ItemsSource" Value="{Binding Children}" />
<Setter Property="Command" Value="{Binding Data.SelectPropertyCommand, 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 SelectedPropertyViewModel.PropertyDescription.Name}"
Visibility="{Binding SelectedPropertyViewModel, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock FontStyle="Italic"
Visibility="{Binding SelectedPropertyViewModel, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
<Run Text="« "/><Run Text="{Binding Placeholder}"/><Run Text=" »"/>
</TextBlock>
</Grid>
</Button>
</UserControl>
@@ -0,0 +1,26 @@
using System.Windows;
using System.Windows.Controls;
namespace Artemis.UI.Shared
{
/// <summary>
/// Interaction logic for DataModelSelectionView.xaml
/// </summary>
public partial class DataModelSelectionView : UserControl
{
public DataModelSelectionView()
{
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;
}
}
}
}
@@ -0,0 +1,134 @@
using System;
using System.Timers;
using System.Windows.Media;
using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Stylet;
// Remove, annoying while working on it
#pragma warning disable 1591
namespace Artemis.UI.Shared
{
public class DataModelSelectionViewModel : PropertyChangedBase
{
private readonly IDataModelUIService _dataModelUIService;
private readonly Module _module;
private readonly Timer _updateTimer;
private Brush _buttonBrush = new SolidColorBrush(Color.FromRgb(171, 71, 188));
private DataModelPropertiesViewModel _dataModelViewModel;
private bool _isDataModelViewModelOpen;
private bool _isEnabled;
private string _placeholder = "Select a property";
private DataModelVisualizationViewModel _selectedPropertyViewModel;
public DataModelSelectionViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService)
{
_module = module;
_dataModelUIService = dataModelUIService;
_updateTimer = new Timer(500);
ShowDataModelValues = settingsService.GetSetting<bool>("ProfileEditor.ShowDataModelValues");
SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand);
Initialize();
}
public string Placeholder
{
get => _placeholder;
set => SetAndNotify(ref _placeholder, value);
}
public Brush ButtonBrush
{
get => _buttonBrush;
set => SetAndNotify(ref _buttonBrush, value);
}
public DelegateCommand SelectPropertyCommand { get; }
public Type[] FilterTypes { get; set; }
public PluginSetting<bool> ShowDataModelValues { get; }
public bool IsEnabled
{
get => _isEnabled;
set => SetAndNotify(ref _isEnabled, value);
}
public DataModelPropertiesViewModel DataModelViewModel
{
get => _dataModelViewModel;
private set => SetAndNotify(ref _dataModelViewModel, value);
}
public bool IsDataModelViewModelOpen
{
get => _isDataModelViewModelOpen;
set => SetAndNotify(ref _isDataModelViewModelOpen, value);
}
public DataModelVisualizationViewModel SelectedPropertyViewModel
{
get => _selectedPropertyViewModel;
private set => SetAndNotify(ref _selectedPropertyViewModel, value);
}
public void PopulateSelectedPropertyViewModel(DataModel datamodel, string path)
{
if (datamodel == null)
SelectedPropertyViewModel = null;
else
SelectedPropertyViewModel = DataModelViewModel.GetChildByPath(datamodel.PluginInfo.Guid, path);
}
private void Initialize()
{
// Get the data models
DataModelViewModel = _dataModelUIService.GetMainDataModelVisualization();
if (!_dataModelUIService.GetPluginExtendsDataModel(_module))
DataModelViewModel.Children.Add(_dataModelUIService.GetPluginDataModelVisualization(_module));
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
_updateTimer.Start();
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
}
private void DataModelOnUpdateRequested(object sender, EventArgs e)
{
DataModelViewModel.ApplyTypeFilter(true, FilterTypes);
}
private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
{
if (IsDataModelViewModelOpen)
DataModelViewModel.Update(_dataModelUIService);
}
private void ExecuteSelectPropertyCommand(object context)
{
if (!(context is DataModelVisualizationViewModel selected))
return;
SelectedPropertyViewModel = selected;
OnPropertySelected(new DataModelPropertySelectedEventArgs(selected));
}
#region Events
public event EventHandler<DataModelPropertySelectedEventArgs> PropertySelected;
protected virtual void OnPropertySelected(DataModelPropertySelectedEventArgs e)
{
PropertySelected?.Invoke(this, e);
}
#endregion
}
}
@@ -0,0 +1,14 @@
using System;
namespace Artemis.UI.Shared
{
public class DataModelPropertySelectedEventArgs : EventArgs
{
public DataModelVisualizationViewModel DataModelVisualizationViewModel { get; }
public DataModelPropertySelectedEventArgs(DataModelVisualizationViewModel dataModelVisualizationViewModel)
{
DataModelVisualizationViewModel = dataModelVisualizationViewModel;
}
}
}
@@ -0,0 +1,99 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared">
<Style x:Key="DisplayConditionButton" TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignFlatAccentBgButton}">
<Setter Property="Margin" Value="3 0" />
<Setter Property="Padding" Value="6 4" />
<Setter Property="Height" Value="22" />
<Setter Property="Foreground" Value="{DynamicResource PrimaryHueMidForegroundBrush}" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style x:Key="DisplayConditionButtonLeftClickMenu" TargetType="{x:Type Button}" BasedOn="{StaticResource DisplayConditionButton}">
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="DataModelSelectionTemplate">
<StackPanel>
<StackPanel.Resources>
<DataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}">
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
</DataTemplate>
<DataTemplate DataType="{x:Type dataModel:DataModelListViewModel}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FormatListBulleted" VerticalAlignment="Center" Margin="0 0 5 0" />
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
<Grid>
<!-- Value description -->
<TextBlock Grid.Column="0" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
</Grid>
</DataTemplate>
</StackPanel.Resources>
<ContentPresenter Content="{Binding}" />
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="DataModelSelectionTemplateWithValues">
<StackPanel>
<StackPanel.Resources>
<DataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}">
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
</DataTemplate>
<DataTemplate DataType="{x:Type dataModel:DataModelListViewModel}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FormatListBulleted" VerticalAlignment="Center" Margin="0 0 5 0" />
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
<Grid ToolTip="{Binding PropertyDescription.Description}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Value description -->
<TextBlock Grid.Column="0" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<!-- Value display -->
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding DisplayValue, Mode=OneWay}"
FontFamily="Consolas"
HorizontalAlignment="Right"
Visibility="{Binding ShowToString, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
Margin="15 0.5 0 0" />
<TextBlock Text="null"
FontFamily="Consolas"
HorizontalAlignment="Right"
Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}"
Margin="15 0.5 0 0"
Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<ContentControl s:View.Model="{Binding DisplayViewModel}"
FontFamily="Consolas"
Margin="15 0.5 0 0"
Visibility="{Binding ShowViewModel, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
</StackPanel.Resources>
<ContentPresenter Content="{Binding}" />
</StackPanel>
</ControlTemplate>
</ResourceDictionary>
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Ninject;
using Ninject.Parameters;
@@ -178,6 +179,11 @@ namespace Artemis.UI.Shared.Services
}
}
public DataModelSelectionViewModel GetDataModelSelectionViewModel(Module module)
{
return _kernel.Get<DataModelSelectionViewModel>(new ConstructorArgument("module", module));
}
private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute description, object initialValue)
{
// The view models expecting value types shouldn't be given null, avoid that
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
namespace Artemis.UI.Shared.Services
{
@@ -26,5 +27,7 @@ namespace Artemis.UI.Shared.Services
DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType);
DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback);
DataModelSelectionViewModel GetDataModelSelectionViewModel(Module module);
}
}
@@ -201,7 +201,7 @@ namespace Artemis.UI.Shared.Services
if (existing != null)
{
if (existing.PluginInfo != pluginInfo)
throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {pluginInfo.Name}");
throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {existing.PluginInfo.Name}");
return existing;
}
@@ -0,0 +1,26 @@
using System.Windows;
namespace Artemis.UI.Shared
{
internal class BindingProxy : Freezable
{
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
public object Data
{
get => GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
}
}
@@ -0,0 +1,41 @@
using System;
using System.Windows.Input;
namespace Artemis.UI.Shared
{
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute) : this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}