From 29ef16097583e1318e8d2dd02f02c1205dc0dcf0 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 23 Jul 2022 21:21:57 +0200 Subject: [PATCH] Profile tree - Fixed dragging while renaming Profile tree - Fixed dragging folders onto themselves Profile tree - Suspend keybinds while renaming Brushes - Added brush presets UI Numberbox - Removed double validation error --- .../Models/Profile/ProfileElement.cs | 4 +- .../ProfileEditor/IProfileEditorService.cs | 11 +++ .../ProfileEditor/ProfileEditorService.cs | 14 +++- src/Artemis.UI.Shared/Styles/NumberBox.axaml | 81 ------------------- .../Behaviors/SimpleContextDragBehavior.cs | 2 +- .../BrushPropertyInputViewModel.cs | 10 ++- .../Screens/Device/DevicePropertiesView.axaml | 1 + .../Panels/Playback/PlaybackViewModel.cs | 28 +++++-- .../Behaviors/ProfileTreeViewDropHandler.cs | 3 +- .../Panels/ProfileTree/TreeItemViewModel.cs | 3 + .../Tree/Dialogs/LayerBrushPresetView.axaml | 64 +++++++++++++++ .../Dialogs/LayerBrushPresetView.axaml.cs | 29 +++++++ .../Tree/Dialogs/LayerBrushPresetViewModel.cs | 53 +++++++++++- .../BrushConfigurationWindowView.axaml | 3 +- .../EffectConfigurationWindowView.axaml | 3 +- src/Artemis.UI/packages.lock.json | 5 +- 16 files changed, 211 insertions(+), 103 deletions(-) create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml.cs diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 2f70ff8f8..11ba77cb6 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -342,7 +342,7 @@ namespace Artemis.Core int current = 2; while (true) { - if (Children.All(c => c is Layer && c.Name != $"{baseName} ({current})")) + if (Children.Where(c => c is Layer).All(c => c.Name != $"{baseName} ({current})")) return $"{baseName} ({current})"; current++; } @@ -361,7 +361,7 @@ namespace Artemis.Core int current = 2; while (true) { - if (Children.All(c => c is Folder && c.Name != $"{baseName} ({current})")) + if (Children.Where(c => c is Folder).All(c => c.Name != $"{baseName} ({current})")) return $"{baseName} ({current})"; current++; } diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs index de5c56670..8875fbbc2 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs @@ -50,6 +50,11 @@ public interface IProfileEditorService : IArtemisSharedUIService /// Gets an observable of the suspended state. /// IObservable SuspendedEditing { get; } + + /// + /// Gets an observable of the suspended keybindings state. + /// + IObservable SuspendedKeybindings { get; } /// /// Gets an observable read only collection of all available editor tools. @@ -96,6 +101,12 @@ public interface IProfileEditorService : IArtemisSharedUIService /// /// The new suspended state. void ChangeSuspendedEditing(bool suspend); + + /// + /// Changes the current suspended keybindings state. + /// + /// The new suspended state. + void ChangeSuspendedKeybindings(bool suspend); /// /// Selects the provided keyframe. diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index e8ba019cc..6aef6f9a3 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -30,6 +30,7 @@ internal class ProfileEditorService : IProfileEditorService private readonly BehaviorSubject _profileElementSubject = new(null); private readonly IProfileService _profileService; private readonly BehaviorSubject _suspendedEditingSubject = new(false); + private readonly BehaviorSubject _suspendedKeybindingsSubject = new(false); private readonly BehaviorSubject _timeSubject = new(TimeSpan.Zero); private readonly SourceList _tools; private readonly SourceList _selectedKeyframes; @@ -64,6 +65,7 @@ internal class ProfileEditorService : IProfileEditorService Time = _timeSubject.AsObservable(); Playing = _playingSubject.AsObservable(); SuspendedEditing = _suspendedEditingSubject.AsObservable(); + SuspendedKeybindings = _suspendedKeybindingsSubject.AsObservable(); PixelsPerSecond = _pixelsPerSecondSubject.AsObservable(); Tools = tools; SelectedKeyframes = selectedKeyframes; @@ -80,7 +82,7 @@ internal class ProfileEditorService : IProfileEditorService .Throttle(TimeSpan.FromSeconds(1)) .SelectMany(async _ => await AutoSaveProfileAsync()) .Subscribe(); - + // When the main window closes, stop editing mainWindowService.MainWindowClosed += (_, _) => ChangeCurrentProfileConfiguration(null); } @@ -90,6 +92,7 @@ internal class ProfileEditorService : IProfileEditorService public IObservable LayerProperty { get; } public IObservable History { get; } public IObservable SuspendedEditing { get; } + public IObservable SuspendedKeybindings { get; } public IObservable Time { get; } public IObservable Playing { get; } public IObservable PixelsPerSecond { get; } @@ -138,6 +141,7 @@ internal class ProfileEditorService : IProfileEditorService _profileConfigurationSubject.OnNext(profileConfiguration); ChangeTime(TimeSpan.Zero); + ChangeSuspendedKeybindings(false); } public void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement) @@ -178,6 +182,14 @@ internal class ProfileEditorService : IProfileEditorService } } + public void ChangeSuspendedKeybindings(bool suspend) + { + if (_suspendedKeybindingsSubject.Value == suspend) + return; + + _suspendedKeybindingsSubject.OnNext(suspend); + } + public void SelectKeyframe(ILayerPropertyKeyframe? keyframe, bool expand, bool toggle) { if (keyframe == null) diff --git a/src/Artemis.UI.Shared/Styles/NumberBox.axaml b/src/Artemis.UI.Shared/Styles/NumberBox.axaml index 89630a14c..18819b3b9 100644 --- a/src/Artemis.UI.Shared/Styles/NumberBox.axaml +++ b/src/Artemis.UI.Shared/Styles/NumberBox.axaml @@ -24,83 +24,6 @@ - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Behaviors/SimpleContextDragBehavior.cs b/src/Artemis.UI/Behaviors/SimpleContextDragBehavior.cs index b89c2c65b..bd3ef036c 100644 --- a/src/Artemis.UI/Behaviors/SimpleContextDragBehavior.cs +++ b/src/Artemis.UI/Behaviors/SimpleContextDragBehavior.cs @@ -105,7 +105,7 @@ public class SimpleContextDragBehavior : Behavior private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e) { PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties; - if (!properties.IsLeftButtonPressed) + if (!properties.IsLeftButtonPressed || FocusManager.Instance?.Current is TextBox) return; if (e.Source is not IControl control || AssociatedObject?.DataContext != control.DataContext) return; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs index 208928485..2b572f7be 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -7,6 +7,7 @@ using Artemis.Core.LayerBrushes; using Artemis.Core.Services; using Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs; using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.PropertyInput; @@ -63,7 +64,14 @@ public class BrushPropertyInputViewModel : PropertyInputViewModel _windowService.CreateContentDialog().WithViewModel(out LayerBrushPresetViewModel _, ("layerBrush", layer.LayerBrush)).ShowAsync()); + { + Dispatcher.UIThread.InvokeAsync(() => _windowService.CreateContentDialog() + .WithTitle("Select preset") + .WithViewModel(out LayerBrushPresetViewModel _, ("layerBrush", layer.LayerBrush)) + .WithDefaultButton(ContentDialogButton.Close) + .WithCloseButtonText("Use defaults") + .ShowAsync()); + } } #region Overrides of PropertyInputViewModel diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml b/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml index 55a4305a1..4c1b080a6 100644 --- a/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml +++ b/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml @@ -10,6 +10,7 @@ x:DataType="device:DevicePropertiesViewModel" Icon="/Assets/Images/Logo/application.ico" Title="Artemis | Device Properties" + WindowStartupLocation="CenterOwner" Width="1250" Height="900"> diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs index e2186f46d..e05a3158c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs @@ -6,6 +6,8 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared; using Artemis.UI.Shared.Services.ProfileEditor; +using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Threading; using ReactiveUI; @@ -23,6 +25,8 @@ public class PlaybackViewModel : ActivatableViewModelBase private bool _repeating; private bool _repeatSegment; private bool _repeatTimeline; + private ReactiveCommand? _togglePlay; + private ReactiveCommand? _playFromStart; public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService) { @@ -36,15 +40,15 @@ public class PlaybackViewModel : ActivatableViewModelBase _currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d); _formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d); _playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d); - _lastUpdate = DateTime.MinValue; DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update); updateTimer.Start(); Disposable.Create(() => updateTimer.Stop()).DisposeWith(d); + + PlayFromStart = ReactiveCommand.Create(ExecutePlayFromStart, _profileEditorService.SuspendedKeybindings.Select(s => !s)).DisposeWith(d); + TogglePlay = ReactiveCommand.Create(ExecuteTogglePlay, _profileEditorService.SuspendedKeybindings.Select(s => !s)).DisposeWith(d); }); - - PlayFromStart = ReactiveCommand.Create(ExecutePlayFromStart); - TogglePlay = ReactiveCommand.Create(ExecuteTogglePlay); + GoToStart = ReactiveCommand.Create(ExecuteGoToStart); GoToEnd = ReactiveCommand.Create(ExecuteGoToEnd); GoToPreviousFrame = ReactiveCommand.Create(ExecuteGoToPreviousFrame); @@ -73,9 +77,19 @@ public class PlaybackViewModel : ActivatableViewModelBase get => _repeatSegment; set => RaiseAndSetIfChanged(ref _repeatSegment, value); } - - public ReactiveCommand PlayFromStart { get; } - public ReactiveCommand TogglePlay { get; } + + public ReactiveCommand? PlayFromStart + { + get => _playFromStart; + set => RaiseAndSetIfChanged(ref _playFromStart, value); + } + + public ReactiveCommand? TogglePlay + { + get => _togglePlay; + set => RaiseAndSetIfChanged(ref _togglePlay, value); + } + public ReactiveCommand GoToStart { get; } public ReactiveCommand GoToEnd { get; } public ReactiveCommand GoToPreviousFrame { get; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Behaviors/ProfileTreeViewDropHandler.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Behaviors/ProfileTreeViewDropHandler.cs index a85d5e82d..5c86009fd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Behaviors/ProfileTreeViewDropHandler.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Behaviors/ProfileTreeViewDropHandler.cs @@ -49,7 +49,8 @@ public class ProfileTreeViewDropHandler : DropHandlerBase IVisual? targetVisual = treeView.GetVisualAt(position).FindAncestorOfType(); if (sourceContext is not T sourceNode || targetContext is not ProfileTreeViewModel vm || targetVisual is not IControl {DataContext: T targetNode}) return false; - + if (bExecute && targetNode == sourceNode) + return false; TreeItemViewModel? sourceParent = sourceNode.Parent; TreeItemViewModel? targetParent = targetNode.Parent; ObservableCollection sourceNodes = sourceParent is { } ? sourceParent.Children : vm.Children; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 7f9e55f14..71500bec7 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -140,11 +140,13 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase ProfileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue)); Renaming = false; + ProfileEditorService.ChangeSuspendedKeybindings(false); } public void CancelRename() { Renaming = false; + ProfileEditorService.ChangeSuspendedKeybindings(false); } public void InsertElement(TreeItemViewModel elementViewModel, int targetIndex) @@ -237,6 +239,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase { Renaming = true; RenameValue = ProfileElement?.Name; + ProfileEditorService.ChangeSuspendedKeybindings(true); } private void ExecuteAddFolder() diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml new file mode 100644 index 000000000..d69042659 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + None of the presets match your search + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml.cs new file mode 100644 index 000000000..06029a66f --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetView.axaml.cs @@ -0,0 +1,29 @@ +using Artemis.Core.LayerBrushes; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs; + +public partial class LayerBrushPresetView : ReactiveUserControl +{ + public LayerBrushPresetView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + if (sender is not IDataContextProvider {DataContext: ILayerBrushPreset preset} || ViewModel == null) + return; + + ViewModel?.SelectPreset(preset); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs index 9b614e488..b90c405af 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs @@ -1,14 +1,61 @@ -using Artemis.Core.LayerBrushes; +using System; +using System.Collections.ObjectModel; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.Core.LayerBrushes; +using Artemis.Core.LayerEffects; +using Artemis.Core.Services; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using DynamicData; +using ReactiveUI; namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs; public class LayerBrushPresetViewModel : ContentDialogViewModelBase { + private readonly BaseLayerBrush _layerBrush; + private string? _searchText; + public LayerBrushPresetViewModel(BaseLayerBrush layerBrush) { - LayerBrush = layerBrush; + _layerBrush = layerBrush; + + SourceList presetsSourceList = new(); + if (layerBrush.Presets != null) + presetsSourceList.AddRange(layerBrush.Presets); + IObservable> presetsFilter = this.WhenAnyValue(vm => vm.SearchText).Select(CreatePredicate); + + presetsSourceList.Connect() + .Filter(presetsFilter) + .Bind(out ReadOnlyObservableCollection presets) + .Subscribe(); + Presets = presets; } - public BaseLayerBrush LayerBrush { get; } + public ReadOnlyObservableCollection Presets { get; } + + public string? SearchText + { + get => _searchText; + set => RaiseAndSetIfChanged(ref _searchText, value); + } + + public void SelectPreset(ILayerBrushPreset preset) + { + _layerBrush.BaseProperties?.ResetAllLayerProperties(); + preset.Apply(); + ContentDialog?.Hide(); + } + + private Func CreatePredicate(string? search) + { + if (string.IsNullOrWhiteSpace(search)) + return _ => true; + + search = search.Trim(); + return data => data.Name.Contains(search, StringComparison.InvariantCultureIgnoreCase) || + data.Description.Contains(search, StringComparison.InvariantCultureIgnoreCase); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml index 12bd2cd1e..40e5d4c30 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml @@ -9,7 +9,8 @@ x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.BrushConfigurationWindowView" Title="Artemis | Brush configuration" Width="{Binding Configuration.DialogWidth}" - Height="{Binding Configuration.DialogHeight}"> + Height="{Binding Configuration.DialogHeight}" + WindowStartupLocation="CenterOwner"> diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml index 3c11ca511..e91b5fa1c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml @@ -9,7 +9,8 @@ x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.EffectConfigurationWindowView" Title="Artemis | Effect configuration" Width="{Binding Configuration.DialogWidth}" - Height="{Binding Configuration.DialogHeight}"> + Height="{Binding Configuration.DialogHeight}" + WindowStartupLocation="CenterOwner"> diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index ae29f6df0..11811f43e 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -1680,10 +1680,7 @@ "Unosquare.Swan.Lite": { "type": "Transitive", "resolved": "3.0.0", - "contentHash": "noPwJJl1Q9uparXy1ogtkmyAPGNfSGb0BLT1292nFH1jdMKje6o2kvvrQUvF9Xklj+IoiAI0UzF6Aqxlvo10lw==", - "dependencies": { - "System.ValueTuple": "4.5.0" - } + "contentHash": "noPwJJl1Q9uparXy1ogtkmyAPGNfSGb0BLT1292nFH1jdMKje6o2kvvrQUvF9Xklj+IoiAI0UzF6Aqxlvo10lw==" }, "artemis.core": { "type": "Project",