diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index f52502575..5abaccfd3 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs b/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs new file mode 100644 index 000000000..b564ccb87 --- /dev/null +++ b/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs @@ -0,0 +1,34 @@ +using System.Windows; +using System.Windows.Controls; +using Microsoft.Xaml.Behaviors; + +namespace Artemis.UI.Shared.Behaviors +{ + public class PutCursorAtEndTextBoxBehavior : Behavior + { + 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; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs index aaf767c55..100ed4e52 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs @@ -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() + /// + public sealed override void Submit() { + OnSubmit(); + UpdateCallback(InputValue, true); + } + + /// + public sealed override void Cancel() + { + OnCancel(); + UpdateCallback(InputValue, false); } } /// /// For internal use only, implement instead. /// - public abstract class DataModelInputViewModel : PropertyChangedBase + public abstract class DataModelInputViewModel : PropertyChangedBase, IViewAware { /// /// Prevents this type being implemented directly, implement instead. /// // ReSharper disable once UnusedMember.Global internal abstract object InternalGuard { get; } + + internal Action 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; } + + /// + /// Submits the input value and removes this view model + /// + public abstract void Submit(); + + /// + /// Discards changes to the input value and removes this view model + /// + public abstract void Cancel(); + + /// + /// Called before the current value is submitted + /// + protected virtual void OnSubmit() + { + } + + /// + /// Called before the current value is discarded + /// + protected virtual void OnCancel() + { + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs index b259f66d3..672e7ff9b 100644 --- a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs @@ -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 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 updateCallback); IReadOnlyCollection RegisteredDataModelEditors { get; } IReadOnlyCollection RegisteredDataModelDisplays { get; } } diff --git a/src/Artemis.UI/Behaviors/MouseBehaviour.cs b/src/Artemis.UI/Behaviors/MouseBehavior.cs similarity index 91% rename from src/Artemis.UI/Behaviors/MouseBehaviour.cs rename to src/Artemis.UI/Behaviors/MouseBehavior.cs index 806822804..184bdf765 100644 --- a/src/Artemis.UI/Behaviors/MouseBehaviour.cs +++ b/src/Artemis.UI/Behaviors/MouseBehavior.cs @@ -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) diff --git a/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml b/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml index d517649a5..f8edaeef5 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml +++ b/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml @@ -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"> - + + + + + diff --git a/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputViewModel.cs b/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputViewModel.cs index f88355001..7fd951fa7 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputViewModel.cs +++ b/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputViewModel.cs @@ -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; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Events/MainWindowKeyEvent.cs b/src/Artemis.UI/Events/MainWindowKeyEvent.cs index f5ac6d8e0..6ad1c3268 100644 --- a/src/Artemis.UI/Events/MainWindowKeyEvent.cs +++ b/src/Artemis.UI/Events/MainWindowKeyEvent.cs @@ -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; } } diff --git a/src/Artemis.UI/Events/MainWindowMouseEvent.cs b/src/Artemis.UI/Events/MainWindowMouseEvent.cs new file mode 100644 index 000000000..4ddb484e9 --- /dev/null +++ b/src/Artemis.UI/Events/MainWindowMouseEvent.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml index cf0b1a06a..6ec5dcdc9 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml @@ -79,7 +79,7 @@ Grid.Column="2" Style="{StaticResource DisplayConditionButtonLeftClickMenu}" Background="#7B7B7B" - BorderBrush="#7B7B7B" + BorderBrush="#7B7B7B" Content="{Binding DisplayConditionPredicate.Operator.Description}" Click="PropertyButton_OnClick"> @@ -87,8 +87,8 @@ - - + + @@ -139,26 +139,30 @@ - + + Width="140" + HorizontalAlignment="Left"> - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index d502bfaff..288420fc4 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -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, IHandle { private readonly IDataModelService _dataModelService; private readonly IDataModelVisualizationService _dataModelVisualizationService; + private readonly IEventAggregator _eventAggregator; private readonly IProfileEditorService _profileEditorService; private DataModelPropertiesViewModel _leftSideDataModel; private List _operators; @@ -28,13 +33,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private List _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) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml index b08a06563..446ac79ac 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml @@ -27,7 +27,7 @@ Visibility="{Binding HasLayerEffectDescriptors, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"> diff --git a/src/Artemis.UI/Screens/RootView.xaml b/src/Artemis.UI/Screens/RootView.xaml index 592a434e3..263ec1c2c 100644 --- a/src/Artemis.UI/Screens/RootView.xaml +++ b/src/Artemis.UI/Screens/RootView.xaml @@ -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}">