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

UI - Rename folders and layers in a dialog to avoid hotkey issues

UI - Fixed renaming an effect executing twice, requiring two undo-actions to undo it
UI - Always disable editor hotkeys when typing in any input
This commit is contained in:
Robert 2023-04-19 16:03:57 +02:00
parent 0b5866a2b8
commit 192b4f0df2
20 changed files with 166 additions and 147 deletions

View File

@ -51,11 +51,6 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// </summary> /// </summary>
IObservable<bool> SuspendedEditing { get; } IObservable<bool> SuspendedEditing { get; }
/// <summary>
/// Gets an observable of the suspended keybindings state.
/// </summary>
IObservable<bool> SuspendedKeybindings { get; }
/// <summary> /// <summary>
/// Gets an observable of the suspended keybindings state. /// Gets an observable of the suspended keybindings state.
/// </summary> /// </summary>
@ -102,12 +97,6 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// <param name="suspend">The new suspended state.</param> /// <param name="suspend">The new suspended state.</param>
void ChangeSuspendedEditing(bool suspend); void ChangeSuspendedEditing(bool suspend);
/// <summary>
/// Changes the current suspended keybindings state.
/// </summary>
/// <param name="suspend">The new suspended state.</param>
void ChangeSuspendedKeybindings(bool suspend);
/// <summary> /// <summary>
/// Changes the current focus mode. /// Changes the current focus mode.
/// </summary> /// </summary>

View File

@ -31,7 +31,6 @@ internal class ProfileEditorService : IProfileEditorService
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private readonly SourceList<ILayerPropertyKeyframe> _selectedKeyframes; private readonly SourceList<ILayerPropertyKeyframe> _selectedKeyframes;
private readonly BehaviorSubject<bool> _suspendedEditingSubject = new(false); private readonly BehaviorSubject<bool> _suspendedEditingSubject = new(false);
private readonly BehaviorSubject<bool> _suspendedKeybindingsSubject = new(false);
private readonly BehaviorSubject<TimeSpan> _timeSubject = new(TimeSpan.Zero); private readonly BehaviorSubject<TimeSpan> _timeSubject = new(TimeSpan.Zero);
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ProfileEditorCommandScope? _profileEditorHistoryScope; private ProfileEditorCommandScope? _profileEditorHistoryScope;
@ -61,7 +60,6 @@ internal class ProfileEditorService : IProfileEditorService
Time = _timeSubject.AsObservable(); Time = _timeSubject.AsObservable();
Playing = _playingSubject.AsObservable(); Playing = _playingSubject.AsObservable();
SuspendedEditing = _suspendedEditingSubject.AsObservable(); SuspendedEditing = _suspendedEditingSubject.AsObservable();
SuspendedKeybindings = _suspendedKeybindingsSubject.AsObservable();
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable(); PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
FocusMode = _focusModeSubject.AsObservable(); FocusMode = _focusModeSubject.AsObservable();
SelectedKeyframes = selectedKeyframes; SelectedKeyframes = selectedKeyframes;
@ -210,7 +208,6 @@ internal class ProfileEditorService : IProfileEditorService
_profileConfigurationSubject.OnNext(profileConfiguration); _profileConfigurationSubject.OnNext(profileConfiguration);
ChangeTime(TimeSpan.Zero); ChangeTime(TimeSpan.Zero);
ChangeSuspendedKeybindings(false);
} }
public void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement) public void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement)
@ -255,15 +252,7 @@ internal class ProfileEditorService : IProfileEditorService
ApplyFocusMode(); ApplyFocusMode();
} }
public void ChangeSuspendedKeybindings(bool suspend)
{
if (_suspendedKeybindingsSubject.Value == suspend)
return;
_suspendedKeybindingsSubject.OnNext(suspend);
}
public void ChangeFocusMode(ProfileEditorFocusMode focusMode) public void ChangeFocusMode(ProfileEditorFocusMode focusMode)
{ {
if (_focusModeSubject.Value == focusMode) if (_focusModeSubject.Value == focusMode)

View File

@ -1,8 +1,43 @@
using DryIoc; using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Input;
using IContainer = DryIoc.IContainer;
namespace Artemis.UI.Shared; namespace Artemis.UI.Shared;
internal static class UI /// <summary>
/// Static UI helpers.
/// </summary>
public static class UI
{ {
private static readonly BehaviorSubject<bool> KeyBindingsEnabledSubject = new(false);
static UI()
{
if (KeyboardDevice.Instance != null)
KeyboardDevice.Instance.PropertyChanged += InstanceOnPropertyChanged;
}
/// <summary>
/// Gets the current IoC locator.
/// </summary>
public static IContainer Locator { get; set; } = null!; public static IContainer Locator { get; set; } = null!;
/// <summary>
/// Gets a boolean indicating whether hotkeys are to be disabled.
/// </summary>
public static IObservable<bool> KeyBindingsEnabled { get; } = KeyBindingsEnabledSubject.AsObservable();
private static void InstanceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (KeyboardDevice.Instance == null || e.PropertyName != nameof(KeyboardDevice.FocusedElement))
return;
bool enabled = KeyboardDevice.Instance.FocusedElement is not TextBox;
if (KeyBindingsEnabledSubject.Value != enabled)
KeyBindingsEnabledSubject.OnNext(enabled);
}
} }

View File

@ -58,7 +58,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
_focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d);
_focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d);
_focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d);
_keyBindingsEnabled = profileEditorService.SuspendedKeybindings.Select(s => !s).ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
}); });
AddFolder = ReactiveCommand.Create(ExecuteAddFolder); AddFolder = ReactiveCommand.Create(ExecuteAddFolder);

View File

@ -52,7 +52,7 @@ public class PlaybackViewModel : ActivatableViewModelBase
_currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d); _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); _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); _playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d);
_keyBindingsEnabled = _profileEditorService.SuspendedKeybindings.Select(s => !s).ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
_lastUpdate = DateTime.MinValue; _lastUpdate = DateTime.MinValue;
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update); DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update);

View File

@ -0,0 +1,15 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:contentDialogs="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs.ProfileElementRenameView"
x:DataType="contentDialogs:ProfileElementRenameViewModel">
<StackPanel>
<StackPanel.KeyBindings>
<KeyBinding Gesture="Enter" Command="{CompiledBinding Enter}" />
</StackPanel.KeyBindings>
<TextBox Name="NameTextBox" Text="{CompiledBinding ProfileElementName}" Watermark="Profile element name" />
</StackPanel>
</UserControl>

View File

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using Artemis.UI.Shared.Extensions;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs;
public partial class ProfileElementRenameView : ReactiveUserControl<ProfileElementRenameViewModel>
{
public ProfileElementRenameView()
{
InitializeComponent();
this.WhenActivated(_ =>
{
this.ClearAllDataValidationErrors();
Dispatcher.UIThread.Post(DelayedAutoFocus);
});
}
private async void DelayedAutoFocus()
{
// Don't ask
await Task.Delay(200);
NameTextBox.SelectAll();
NameTextBox.Focus();
}
}

View File

@ -0,0 +1,47 @@
using System.Reactive;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs;
public class ProfileElementRenameViewModel : ContentDialogViewModelBase
{
private readonly IProfileEditorService _profileEditorService;
private readonly ProfileElement _profileElement;
private string? _profileElementName;
public ProfileElementRenameViewModel(IProfileEditorService profileEditorService, ProfileElement profileElement)
{
_profileEditorService = profileEditorService;
_profileElement = profileElement;
_profileElementName = profileElement.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
Enter = ReactiveCommand.Create(() => ContentDialog?.Hide(ContentDialogResult.Primary), Confirm.CanExecute);
this.ValidationRule(vm => vm.ProfileElementName, name => !string.IsNullOrWhiteSpace(name), "You must specify a valid name");
}
public string? ProfileElementName
{
get => _profileElementName;
set => RaiseAndSetIfChanged(ref _profileElementName, value);
}
public ReactiveCommand<Unit, Unit> Enter { get; }
public ReactiveCommand<Unit, Unit> Confirm { get; }
private void ExecuteConfirm()
{
if (ProfileElementName == null)
return;
_profileEditorService.ExecuteCommand(new RenameProfileElement(_profileElement, ProfileElementName));
ContentDialog?.Hide(ContentDialogResult.Primary);
}
}

View File

@ -25,15 +25,8 @@
Kind="FolderOpen" Kind="FolderOpen"
Margin="0 0 5 0" Margin="0 0 5 0"
IsVisible="{Binding IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" /> IsVisible="{Binding IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" />
<TextBox Grid.Column="2"
Margin="-5 0 0 0" <TextBlock Grid.Column="2" Text="{CompiledBinding Folder.Name}" VerticalAlignment="Center" />
Classes="condensed"
IsVisible="{CompiledBinding Renaming}"
Text="{CompiledBinding RenameValue}"
x:Name="Input"
KeyUp="InputElement_OnKeyUp"
LostFocus="InputElement_OnLostFocus" />
<TextBlock Grid.Column="2" IsVisible="{CompiledBinding !Renaming}" Text="{CompiledBinding Folder.Name}" VerticalAlignment="Center" />
<avalonia:MaterialIcon Grid.Column="3" Kind="Eye" IsVisible="{CompiledBinding IsFocused}" ToolTip.Tip="This element is visible because of the current focus mode" Margin="0 0 5 0"/> <avalonia:MaterialIcon Grid.Column="3" Kind="Eye" IsVisible="{CompiledBinding IsFocused}" ToolTip.Tip="This element is visible because of the current focus mode" Margin="0 0 5 0"/>
<ToggleButton Grid.Column="4" <ToggleButton Grid.Column="4"

View File

@ -1,10 +1,4 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
@ -13,27 +7,5 @@ public partial class FolderTreeItemView : ReactiveUserControl<FolderTreeItemView
public FolderTreeItemView() public FolderTreeItemView()
{ {
InitializeComponent(); InitializeComponent();
this.WhenActivated(d =>
{
ViewModel?.Rename.Subscribe(_ =>
{
Input.Focus();
Input.SelectAll();
}).DisposeWith(d);
});
}
private void InputElement_OnKeyUp(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
ViewModel?.SubmitRename();
else if (e.Key == Key.Escape)
ViewModel?.CancelRename();
}
private void InputElement_OnLostFocus(object? sender, RoutedEventArgs e)
{
ViewModel?.CancelRename();
} }
} }

View File

@ -18,15 +18,8 @@
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" /> <avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" />
<TextBox Grid.Column="2"
Margin="-5 0 0 0" <TextBlock Grid.Column="2" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" />
Classes="condensed"
x:Name="Input"
IsVisible="{CompiledBinding Renaming}"
Text="{CompiledBinding RenameValue}"
KeyUp="InputElement_OnKeyUp"
LostFocus="InputElement_OnLostFocus" />
<TextBlock Grid.Column="2" IsVisible="{CompiledBinding !Renaming}" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" />
<avalonia:MaterialIcon Grid.Column="3" Kind="Eye" IsVisible="{CompiledBinding IsFocused}" ToolTip.Tip="This element is visible because of the current focus mode" Margin="0 0 5 0"/> <avalonia:MaterialIcon Grid.Column="3" Kind="Eye" IsVisible="{CompiledBinding IsFocused}" ToolTip.Tip="This element is visible because of the current focus mode" Margin="0 0 5 0"/>
<ToggleButton Grid.Column="4" <ToggleButton Grid.Column="4"

View File

@ -1,10 +1,4 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
@ -13,27 +7,5 @@ public partial class LayerTreeItemView : ReactiveUserControl<LayerTreeItemViewMo
public LayerTreeItemView() public LayerTreeItemView()
{ {
InitializeComponent(); InitializeComponent();
this.WhenActivated(d =>
{
ViewModel?.Rename.Subscribe(_ =>
{
Input.Focus();
Input.SelectAll();
}).DisposeWith(d);
});
}
private void InputElement_OnKeyUp(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
ViewModel?.SubmitRename();
else if (e.Key == Key.Escape)
ViewModel?.CancelRename();
}
private void InputElement_OnLostFocus(object? sender, RoutedEventArgs e)
{
ViewModel?.CancelRename();
} }
} }

View File

@ -45,7 +45,7 @@ public class ProfileTreeViewModel : TreeItemViewModel
_focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d);
_focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d);
_focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d);
_keyBindingsEnabled = profileEditorService.SuspendedKeybindings.Select(s => !s).ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
}); });
ClearSelection = ReactiveCommand.Create(() => profileEditorService.ChangeCurrentProfileElement(null), this.WhenAnyValue(vm => vm.KeyBindingsEnabled)); ClearSelection = ReactiveCommand.Create(() => profileEditorService.ChangeCurrentProfileElement(null), this.WhenAnyValue(vm => vm.KeyBindingsEnabled));

View File

@ -9,9 +9,11 @@ using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.DryIoc.Factories; using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs; using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia; using Avalonia;
@ -30,8 +32,6 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
private bool _isFlyoutOpen; private bool _isFlyoutOpen;
private ObservableAsPropertyHelper<bool>? _isFocused; private ObservableAsPropertyHelper<bool>? _isFocused;
private ProfileElement? _profileElement; private ProfileElement? _profileElement;
private string? _renameValue;
private bool _renaming;
private TimeSpan _time; private TimeSpan _time;
protected TreeItemViewModel(TreeItemViewModel? parent, protected TreeItemViewModel(TreeItemViewModel? parent,
@ -50,7 +50,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
AddLayer = ReactiveCommand.Create(ExecuteAddLayer); AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
AddFolder = ReactiveCommand.Create(ExecuteAddFolder); AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
OpenAdaptionHints = ReactiveCommand.CreateFromTask(ExecuteOpenAdaptionHints, this.WhenAnyValue(vm => vm.ProfileElement).Select(p => p is Layer)); OpenAdaptionHints = ReactiveCommand.CreateFromTask(ExecuteOpenAdaptionHints, this.WhenAnyValue(vm => vm.ProfileElement).Select(p => p is Layer));
Rename = ReactiveCommand.Create(ExecuteRename); Rename = ReactiveCommand.CreateFromTask(ExecuteRename);
Delete = ReactiveCommand.Create(ExecuteDelete); Delete = ReactiveCommand.Create(ExecuteDelete);
Duplicate = ReactiveCommand.CreateFromTask(ExecuteDuplicate); Duplicate = ReactiveCommand.CreateFromTask(ExecuteDuplicate);
Copy = ReactiveCommand.CreateFromTask(ExecuteCopy); Copy = ReactiveCommand.CreateFromTask(ExecuteCopy);
@ -96,12 +96,6 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _isFlyoutOpen, value); set => RaiseAndSetIfChanged(ref _isFlyoutOpen, value);
} }
public bool Renaming
{
get => _renaming;
set => RaiseAndSetIfChanged(ref _renaming, value);
}
public bool CanPaste public bool CanPaste
{ {
get => _canPaste; get => _canPaste;
@ -120,13 +114,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> Paste { get; } public ReactiveCommand<Unit, Unit> Paste { get; }
public ReactiveCommand<Unit, Unit> Delete { get; } public ReactiveCommand<Unit, Unit> Delete { get; }
public abstract bool SupportsChildren { get; } public abstract bool SupportsChildren { get; }
public string? RenameValue
{
get => _renameValue;
set => RaiseAndSetIfChanged(ref _renameValue, value);
}
public async Task ShowBrokenStateExceptions() public async Task ShowBrokenStateExceptions()
{ {
if (ProfileElement == null) if (ProfileElement == null)
@ -143,26 +131,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
return; return;
} }
} }
public void SubmitRename()
{
if (ProfileElement == null)
{
Renaming = false;
return;
}
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) public void InsertElement(TreeItemViewModel elementViewModel, int targetIndex)
{ {
if (elementViewModel.Parent == this && Children.IndexOf(elementViewModel) == targetIndex) if (elementViewModel.Parent == this && Children.IndexOf(elementViewModel) == targetIndex)
@ -250,11 +219,18 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
ProfileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement)); ProfileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement));
} }
private void ExecuteRename() private async Task ExecuteRename()
{ {
Renaming = true; if (ProfileElement == null)
RenameValue = ProfileElement?.Name; return;
ProfileEditorService.ChangeSuspendedKeybindings(true);
await _windowService.CreateContentDialog()
.WithTitle(ProfileElement is Folder ? "Rename folder" : "Rename layer")
.WithViewModel(out ProfileElementRenameViewModel vm, ProfileElement)
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm))
.WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
} }
private void ExecuteAddFolder() private void ExecuteAddFolder()

View File

@ -8,7 +8,7 @@
x:DataType="contentDialogs:LayerEffectRenameViewModel"> x:DataType="contentDialogs:LayerEffectRenameViewModel">
<StackPanel> <StackPanel>
<StackPanel.KeyBindings> <StackPanel.KeyBindings>
<KeyBinding Gesture="Enter" Command="{CompiledBinding Confirm}" /> <KeyBinding Gesture="Enter" Command="{CompiledBinding Enter}" />
</StackPanel.KeyBindings> </StackPanel.KeyBindings>
<TextBox Name="NameTextBox" Text="{CompiledBinding LayerEffectName}" Watermark="Layer effect name" /> <TextBox Name="NameTextBox" Text="{CompiledBinding LayerEffectName}" Watermark="Layer effect name" />
</StackPanel> </StackPanel>

View File

@ -26,5 +26,4 @@ public partial class LayerEffectRenameView : ReactiveUserControl<LayerEffectRena
NameTextBox.SelectAll(); NameTextBox.SelectAll();
NameTextBox.Focus(); NameTextBox.Focus();
} }
} }

View File

@ -22,6 +22,7 @@ public class LayerEffectRenameViewModel : ContentDialogViewModelBase
_layerEffectName = layerEffect.Name; _layerEffectName = layerEffect.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid); Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
Enter = ReactiveCommand.Create(() => ContentDialog?.Hide(ContentDialogResult.Primary), Confirm.CanExecute);
this.ValidationRule(vm => vm.LayerEffectName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name"); this.ValidationRule(vm => vm.LayerEffectName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name");
} }
@ -32,6 +33,7 @@ public class LayerEffectRenameViewModel : ContentDialogViewModelBase
} }
public ReactiveCommand<Unit, Unit> Confirm { get; } public ReactiveCommand<Unit, Unit> Confirm { get; }
public ReactiveCommand<Unit, Unit> Enter { get; }
private void ExecuteConfirm() private void ExecuteConfirm()
{ {

View File

@ -8,7 +8,7 @@
x:DataType="sidebar:SidebarCategoryEditViewModel"> x:DataType="sidebar:SidebarCategoryEditViewModel">
<StackPanel> <StackPanel>
<StackPanel.KeyBindings> <StackPanel.KeyBindings>
<KeyBinding Gesture="Enter" Command="{CompiledBinding Confirm}" /> <KeyBinding Gesture="Enter" Command="{CompiledBinding Enter}" />
</StackPanel.KeyBindings> </StackPanel.KeyBindings>
<TextBox Text="{CompiledBinding CategoryName}" Watermark="Category name"/> <TextBox Text="{CompiledBinding CategoryName}" Watermark="Category name"/>
</StackPanel> </StackPanel>

View File

@ -24,6 +24,7 @@ public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
_categoryName = _category.Name; _categoryName = _category.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid); Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
Enter = ReactiveCommand.Create(() => ContentDialog?.Hide(ContentDialogResult.Primary), Confirm.CanExecute);
this.ValidationRule(vm => vm.CategoryName, categoryName => !string.IsNullOrWhiteSpace(categoryName?.Trim()), "You must specify a valid name"); this.ValidationRule(vm => vm.CategoryName, categoryName => !string.IsNullOrWhiteSpace(categoryName?.Trim()), "You must specify a valid name");
this.ValidationRule(vm => vm.CategoryName, categoryName => profileService.ProfileCategories.All(c => c.Name != categoryName?.Trim()), "You must specify a unique name"); this.ValidationRule(vm => vm.CategoryName, categoryName => profileService.ProfileCategories.All(c => c.Name != categoryName?.Trim()), "You must specify a unique name");
} }
@ -35,6 +36,7 @@ public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
} }
public ReactiveCommand<Unit, Unit> Confirm { get; } public ReactiveCommand<Unit, Unit> Confirm { get; }
public ReactiveCommand<Unit, Unit> Enter { get; }
private void ExecuteConfirm() private void ExecuteConfirm()
{ {

View File

@ -27,6 +27,7 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private bool _pauseUpdate; private bool _pauseUpdate;
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
public NodeScriptWindowViewModel(NodeScript nodeScript, public NodeScriptWindowViewModel(NodeScript nodeScript,
INodeService nodeService, INodeService nodeService,
@ -49,7 +50,7 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
_windowService = windowService; _windowService = windowService;
CreateNode = ReactiveCommand.Create<NodeData>(ExecuteCreateNode); CreateNode = ReactiveCommand.Create<NodeData>(ExecuteCreateNode);
AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange); AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
Export = ReactiveCommand.CreateFromTask(ExecuteExport); Export = ReactiveCommand.CreateFromTask(ExecuteExport);
Import = ReactiveCommand.CreateFromTask(ExecuteImport); Import = ReactiveCommand.CreateFromTask(ExecuteImport);
@ -64,6 +65,8 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Normal, Update); DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Normal, Update);
// TODO: Remove in favor of saving each time a node editor command is executed // TODO: Remove in favor of saving each time a node editor command is executed
DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save); DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save);
@ -89,7 +92,8 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
public ReactiveCommand<Unit, Unit> AutoArrange { get; } public ReactiveCommand<Unit, Unit> AutoArrange { get; }
public ReactiveCommand<Unit, Unit> Export { get; } public ReactiveCommand<Unit, Unit> Export { get; }
public ReactiveCommand<Unit, Unit> Import { get; } public ReactiveCommand<Unit, Unit> Import { get; }
public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true); public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true);