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

Display conditions - Implemented most of static input

This commit is contained in:
SpoinkyNL 2020-07-06 23:41:41 +02:00
parent 2ad78411c8
commit f9e516197f
14 changed files with 248 additions and 30 deletions

View File

@ -34,6 +34,7 @@
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="MaterialDesignExtensions" Version="3.1.0" />
<PackageReference Include="MaterialDesignThemes" Version="3.1.3" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />
<PackageReference Include="Ninject" Version="3.3.4" />
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
<PackageReference Include="SkiaSharp" Version="1.68.3" />

View File

@ -0,0 +1,34 @@
using System.Windows;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;
namespace Artemis.UI.Shared.Behaviors
{
public class PutCursorAtEndTextBoxBehavior : Behavior<UIElement>
{
private TextBox _textBox;
protected override void OnAttached()
{
base.OnAttached();
_textBox = AssociatedObject as TextBox;
if (_textBox == null) return;
_textBox.GotFocus += TextBoxGotFocus;
}
protected override void OnDetaching()
{
if (_textBox == null) return;
_textBox.GotFocus -= TextBoxGotFocus;
base.OnDetaching();
}
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
_textBox.CaretIndex = _textBox.Text.Length;
}
}
}

View File

@ -1,4 +1,6 @@
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using System;
using System.Windows;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Stylet;
namespace Artemis.UI.Shared.DataModelVisualization
@ -22,20 +24,66 @@ namespace Artemis.UI.Shared.DataModelVisualization
public DataModelPropertyAttribute Description { get; }
internal override object InternalGuard { get; } = null;
protected void Submit()
/// <inheritdoc />
public sealed override void Submit()
{
OnSubmit();
UpdateCallback(InputValue, true);
}
/// <inheritdoc />
public sealed override void Cancel()
{
OnCancel();
UpdateCallback(InputValue, false);
}
}
/// <summary>
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
/// </summary>
public abstract class DataModelInputViewModel : PropertyChangedBase
public abstract class DataModelInputViewModel : PropertyChangedBase, IViewAware
{
/// <summary>
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
/// </summary>
// ReSharper disable once UnusedMember.Global
internal abstract object InternalGuard { get; }
internal Action<object, bool> UpdateCallback { get; set; }
public void AttachView(UIElement view)
{
if (View != null)
throw new InvalidOperationException(string.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, GetType().Name));
View = view;
}
public UIElement View { get; set; }
/// <summary>
/// Submits the input value and removes this view model
/// </summary>
public abstract void Submit();
/// <summary>
/// Discards changes to the input value and removes this view model
/// </summary>
public abstract void Cancel();
/// <summary>
/// Called before the current value is submitted
/// </summary>
protected virtual void OnSubmit()
{
}
/// <summary>
/// Called before the current value is discarded
/// </summary>
protected virtual void OnCancel()
{
}
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Artemis.Core.Plugins.Exceptions;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
@ -9,6 +10,8 @@ using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services.Interfaces;
using Ninject;
using Ninject.Parameters;
using Stylet;
namespace Artemis.UI.Shared.Services
{
@ -142,6 +145,27 @@ namespace Artemis.UI.Shared.Services
return null;
}
}
public DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback)
{
lock (_registeredDataModelEditors)
{
var match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType.IsAssignableFrom(propertyType));
if (match != null)
{
var parameters = new IParameter[]
{
new ConstructorArgument("description", description),
new ConstructorArgument("initialValue", initialValue)
};
var viewModel = (DataModelInputViewModel) _kernel.Get(match.ViewModelType, parameters);
viewModel.UpdateCallback = updateCallback;
return viewModel;
}
return null;
}
}
}
public interface IDataModelVisualizationService : IArtemisSharedUIService
@ -162,6 +186,7 @@ namespace Artemis.UI.Shared.Services
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType);
DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback);
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
}

View File

@ -3,11 +3,11 @@ using System.Windows.Input;
namespace Artemis.UI.Behaviors
{
public class MouseBehaviour
public class MouseBehavior
{
public static readonly DependencyProperty MouseUpCommandProperty =
DependencyProperty.RegisterAttached("MouseUpCommand", typeof(ICommand),
typeof(MouseBehaviour), new FrameworkPropertyMetadata(
typeof(MouseBehavior), new FrameworkPropertyMetadata(
MouseUpCommandChanged));
public static void SetMouseUpCommand(UIElement element, ICommand value)

View File

@ -4,7 +4,14 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.DataModelVisualization.Input"
xmlns:System="clr-namespace:System;assembly=System.Runtime"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<TextBox VerticalAlignment="Center" Text="{Binding InputValue}"/>
<TextBox VerticalAlignment="Center" Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}">
<b:Interaction.Behaviors>
<behaviors:PutCursorAtEndTextBoxBehavior/>
</b:Interaction.Behaviors>
</TextBox>
</UserControl>

View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
using Artemis.UI.Shared.DataModelVisualization;
namespace Artemis.UI.DataModelVisualization.Input
@ -11,5 +8,11 @@ namespace Artemis.UI.DataModelVisualization.Input
public StringDataModelInputViewModel(DataModelPropertyAttribute description, string initialValue) : base(description, initialValue)
{
}
protected override void OnSubmit()
{
if (string.IsNullOrWhiteSpace(InputValue))
InputValue = null;
}
}
}

View File

@ -4,12 +4,14 @@ namespace Artemis.UI.Events
{
public class MainWindowKeyEvent
{
public MainWindowKeyEvent(bool keyDown, KeyEventArgs eventArgs)
public MainWindowKeyEvent(object sender, bool keyDown, KeyEventArgs eventArgs)
{
Sender = sender;
KeyDown = keyDown;
EventArgs = eventArgs;
}
public object Sender { get; }
public bool KeyDown { get; }
public KeyEventArgs EventArgs { get; }
}

View File

@ -0,0 +1,18 @@
using System.Windows.Input;
namespace Artemis.UI.Events
{
public class MainWindowMouseEvent
{
public MainWindowMouseEvent(object sender, bool keyDown, MouseEventArgs eventArgs)
{
Sender = sender;
MouseDown = keyDown;
EventArgs = eventArgs;
}
public object Sender { get; }
public bool MouseDown { get; }
public MouseEventArgs EventArgs { get; }
}
}

View File

@ -79,7 +79,7 @@
Grid.Column="2"
Style="{StaticResource DisplayConditionButtonLeftClickMenu}"
Background="#7B7B7B"
BorderBrush="#7B7B7B"
BorderBrush="#7B7B7B"
Content="{Binding DisplayConditionPredicate.Operator.Description}"
Click="PropertyButton_OnClick">
<Button.ContextMenu>
@ -87,8 +87,8 @@
<ContextMenu.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding Icon}" VerticalAlignment="Center" Margin="0 0 15 0"/>
<TextBlock Text="{Binding Description}" VerticalAlignment="Center"/>
<materialDesign:PackIcon Kind="{Binding Icon}" VerticalAlignment="Center" Margin="0 0 15 0" />
<TextBlock Text="{Binding Description}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ContextMenu.ItemTemplate>
@ -139,26 +139,30 @@
<Grid Grid.Row="0"
Grid.Column="3"
Visibility="{Binding ShowRightSidePropertySelection, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<materialDesign:Transitioner
SelectedIndex="{Binding RightSideTransitionIndex}"
DefaultTransitionOrigin="0.5, 0.5"
Margin="3 -4">
<materialDesign:Transitioner SelectedIndex="{Binding RightSideTransitionIndex}" DefaultTransitionOrigin="0.5, 0.5" Margin="3 -4">
<Button Style="{StaticResource DisplayConditionButton}"
Background="{DynamicResource PrimaryHueMidBrush}"
BorderBrush="{DynamicResource PrimaryHueMidBrush}"
Margin="0 4"
Content="PredicateRightSide"
Command="{s:Action ActivateRightSideInputViewModel}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left">
<Grid>
<TextBlock Text="{Binding DisplayConditionPredicate.RightStaticValue}"
Visibility="{Binding DisplayConditionPredicate.RightStaticValue, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock Text="« Enter a value »"
FontStyle="Italic"
Visibility="{Binding DisplayConditionPredicate.RightStaticValue, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}" />
</Grid>
</Button>
<Border BorderBrush="{DynamicResource PrimaryHueMidBrush}"
BorderThickness="2"
Background="{DynamicResource MaterialDesignPaper}"
CornerRadius="3"
Padding="3"
Width="140">
Width="140"
HorizontalAlignment="Left">
<ContentControl s:View.Model="{Binding RightSideInputViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</Border>
</materialDesign:Transitioner>
</Grid>
</Grid>
</UserControl>

View File

@ -2,21 +2,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Events;
using Artemis.UI.Screens.Module.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.Module.ProfileEditor.DisplayConditions
{
public class DisplayConditionPredicateViewModel : DisplayConditionViewModel
public class DisplayConditionPredicateViewModel : DisplayConditionViewModel, IHandle<MainWindowKeyEvent>, IHandle<MainWindowMouseEvent>
{
private readonly IDataModelService _dataModelService;
private readonly IDataModelVisualizationService _dataModelVisualizationService;
private readonly IEventAggregator _eventAggregator;
private readonly IProfileEditorService _profileEditorService;
private DataModelPropertiesViewModel _leftSideDataModel;
private List<DisplayConditionOperator> _operators;
@ -28,13 +33,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
private List<Type> _supportedInputTypes;
public DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent,
IProfileEditorService profileEditorService, IDataModelVisualizationService dataModelVisualizationService, IDataModelService dataModelService)
public DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate,
DisplayConditionViewModel parent,
IProfileEditorService profileEditorService,
IDataModelVisualizationService dataModelVisualizationService,
IDataModelService dataModelService,
IEventAggregator eventAggregator)
: base(displayConditionPredicate, parent)
{
_profileEditorService = profileEditorService;
_dataModelVisualizationService = dataModelVisualizationService;
_dataModelService = dataModelService;
_eventAggregator = eventAggregator;
SelectLeftPropertyCommand = new DelegateCommand(ExecuteSelectLeftProperty);
SelectRightPropertyCommand = new DelegateCommand(ExecuteSelectRightProperty);
@ -48,6 +58,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public DelegateCommand SelectRightPropertyCommand { get; }
public DelegateCommand SelectOperatorCommand { get; }
public bool ShowRightSidePropertySelection => DisplayConditionPredicate.PredicateType == PredicateType.Dynamic;
public bool CanActivateRightSideInputViewModel => SelectedLeftSideProperty?.PropertyInfo != null;
public bool IsInitialized { get; private set; }
@ -66,7 +77,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public DataModelVisualizationViewModel SelectedLeftSideProperty
{
get => _selectedLeftSideProperty;
set => SetAndNotify(ref _selectedLeftSideProperty, value);
set
{
if (!SetAndNotify(ref _selectedLeftSideProperty, value)) return;
NotifyOfPropertyChange(nameof(CanActivateRightSideInputViewModel));
}
}
public DataModelVisualizationViewModel SelectedRightSideProperty
@ -93,6 +108,26 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
set => SetAndNotify(ref _operators, value);
}
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 void Initialize()
{
Task.Run(() =>
@ -155,7 +190,37 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
public void ActivateRightSideInputViewModel()
{
if (SelectedLeftSideProperty?.PropertyInfo == null)
return;
RightSideTransitionIndex = 1;
RightSideInputViewModel = _dataModelVisualizationService.GetDataModelInputViewModel(
SelectedLeftSideProperty.PropertyInfo.PropertyType,
SelectedLeftSideProperty.PropertyDescription,
DisplayConditionPredicate.RightStaticValue,
UpdateInputValue
);
_eventAggregator.Subscribe(this);
// After the animation finishes attempt to focus the input field
Task.Run(async () =>
{
await Task.Delay(400);
await Execute.OnUIThreadAsync(() => RightSideInputViewModel.View.MoveFocus(new TraversalRequest(FocusNavigationDirection.First)));
});
}
public void UpdateInputValue(object value, bool isSubmitted)
{
if (isSubmitted)
{
DisplayConditionPredicate.RightStaticValue = value;
Update();
}
RightSideTransitionIndex = 0;
RightSideInputViewModel = null;
_eventAggregator.Unsubscribe(this);
}
private void ExecuteSelectLeftProperty(object context)

View File

@ -27,7 +27,7 @@
Visibility="{Binding HasLayerEffectDescriptors, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="behaviors:MouseBehaviour.MouseUpCommand" Value="{x:Static materialDesign:Transitioner.MoveFirstCommand}" />
<Setter Property="behaviors:MouseBehavior.MouseUpCommand" Value="{x:Static materialDesign:Transitioner.MoveFirstCommand}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>

View File

@ -20,6 +20,8 @@
Activated="{s:Action WindowActivated}"
KeyDown="{s:Action WindowKeyDown}"
KeyUp="{s:Action WindowKeyUp}"
MouseDown="{s:Action WindowMouseDown}"
MouseUp="{s:Action WindowMouseUp}"
d:DesignHeight="640" d:DesignWidth="1200" d:DataContext="{d:DesignInstance screens:RootViewModel}">
<mde:MaterialWindow.Resources>
<Style TargetType="ContentControl" x:Key="InitializingFade">

View File

@ -105,12 +105,21 @@ namespace Artemis.UI.Screens
public void WindowKeyDown(object sender, KeyEventArgs e)
{
_eventAggregator.Publish(new MainWindowKeyEvent(true, e));
_eventAggregator.Publish(new MainWindowKeyEvent(sender, true, e));
}
public void WindowKeyUp(object sender, KeyEventArgs e)
{
_eventAggregator.Publish(new MainWindowKeyEvent(false, e));
_eventAggregator.Publish(new MainWindowKeyEvent(sender, false, e));
}
public void WindowMouseDown(object sender, MouseButtonEventArgs e)
{
_eventAggregator.Publish(new MainWindowMouseEvent(sender, true, e));
}
public void WindowMouseUp(object sender, MouseButtonEventArgs e)
{
_eventAggregator.Publish(new MainWindowMouseEvent(sender, false, e));
}