From 5802855eb1296017c1b8ec4d65cd50d72a0b4584 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 10 Jan 2022 21:19:35 +0100 Subject: [PATCH] Profile editor - Added layer renaming Profile editor - Added undo/redo notification (for now) --- .../Services/Builders/NotificationBuilder.cs | 441 +++++++++--------- .../Commands/RenameProfileElement.cs | 51 ++ .../ProfileEditor/ProfileEditorHistory.cs | 16 +- .../ProfileEditor/ProfileEditorService.cs | 4 +- .../Artemis.UI.Shared/Styles/Artemis.axaml | 1 + .../Artemis.UI.Shared/Styles/TextBox.axaml | 28 ++ .../Panels/MenuBar/MenuBarViewModel.cs | 34 +- .../Tree/TreeGroupView.axaml | 30 +- .../Tree/TreeGroupView.axaml.cs | 7 + .../ProfileTree/FolderTreeItemView.axaml | 10 +- .../ProfileTree/FolderTreeItemView.axaml.cs | 22 + .../ProfileTree/FolderTreeItemViewModel.cs | 2 + .../ProfileTree/LayerTreeItemView.axaml | 10 +- .../ProfileTree/LayerTreeItemView.axaml.cs | 22 + .../Panels/ProfileTree/ProfileTreeView.axaml | 20 +- .../ProfileTree/ProfileTreeViewModel.cs | 5 +- .../Panels/ProfileTree/TreeItemViewModel.cs | 38 ++ 17 files changed, 492 insertions(+), 249 deletions(-) create mode 100644 src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/RenameProfileElement.cs create mode 100644 src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs b/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs index dab6ab14e..6e14d181f 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs @@ -8,233 +8,236 @@ using FluentAvalonia.UI.Controls; using ReactiveUI; using Button = Avalonia.Controls.Button; -namespace Artemis.UI.Shared.Services.Builders +namespace Artemis.UI.Shared.Services.Builders; + +/// +/// Represents a builder that can be used to create notifications. +/// +public class NotificationBuilder +{ + private readonly InfoBar _infoBar; + private readonly Window _parent; + private TimeSpan _timeout = TimeSpan.FromSeconds(5); + + /// + /// Creates a new instance of the class. + /// + /// The parent window that will host the notification. + public NotificationBuilder(Window parent) + { + _parent = parent; + _infoBar = new InfoBar + { + Classes = Classes.Parse("notification-info-bar"), + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Right + }; + } + + /// + /// Changes the title of the notification. + /// + /// The new title. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder WithTitle(string? title) + { + _infoBar.Title = title; + return this; + } + + /// + /// Changes the message of the notification. + /// + /// The new message. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder WithMessage(string? content) + { + _infoBar.Message = content; + return this; + } + + /// + /// Changes the timeout of the notification after which it disappears automatically. + /// + /// The timeout of the notification after which it disappears automatically. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder WithTimeout(TimeSpan timeout) + { + _timeout = timeout; + return this; + } + + /// + /// Changes the vertical position of the notification inside the parent window. + /// + /// The vertical position of the notification inside the parent window. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder WithVerticalPosition(VerticalAlignment position) + { + _infoBar.VerticalAlignment = position; + return this; + } + + /// + /// Changes the horizontal position of the notification inside the parent window. + /// + /// The horizontal position of the notification inside the parent window. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder WithHorizontalPosition(HorizontalAlignment position) + { + _infoBar.HorizontalAlignment = position; + return this; + } + + /// + /// Changes the severity (color) of the notification. + /// + /// The severity (color) of the notification. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder WithSeverity(NotificationSeverity severity) + { + _infoBar.Severity = (InfoBarSeverity) severity; + return this; + } + + /// + /// Changes the action button of the notification. + /// + /// An action to configure the button. + /// The notification builder that can be used to further build the notification. + public NotificationBuilder HavingButton(Action configure) + { + NotificationButtonBuilder builder = new(); + configure(builder); + _infoBar.ActionButton = builder.Build(); + + return this; + } + + /// + /// Shows the notification. + /// + /// An action that can be called to hide the notification. + /// + public Action Show() + { + if (_parent.Content is not Panel panel) + throw new ArtemisSharedUIException("Can't display a notification on a window without a panel at its root."); + + Dispatcher.UIThread.Post(() => + { + panel.Children.Add(_infoBar); + _infoBar.Closed += InfoBarOnClosed; + _infoBar.IsOpen = true; + }); + + Task.Run(async () => + { + await Task.Delay(_timeout); + Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false); + }); + + return () => Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false); + } + + private void InfoBarOnClosed(InfoBar sender, InfoBarClosedEventArgs args) + { + _infoBar.Closed -= InfoBarOnClosed; + if (_parent.Content is not Panel panel) + return; + + panel.Children.Remove(_infoBar); + } +} + +/// +/// Represents a builder that can be used to create buttons inside notifications. +/// +public class NotificationButtonBuilder +{ + private Action? _action; + private ICommand? _command; + private object? _commandParameter; + private string _text = "Text"; + + /// + /// Changes text message of the button. + /// + /// The new text. + /// The notification builder that can be used to further build the button. + public NotificationButtonBuilder WithText(string text) + { + _text = text; + return this; + } + + /// + /// Changes action that is called when the button is clicked. + /// + /// The action to call when the button is clicked. + /// The builder that can be used to further build the button. + public NotificationButtonBuilder WithAction(Action action) + { + _command = null; + _action = action; + return this; + } + + /// + /// Changes command that is called when the button is clicked. + /// + /// The command to call when the button is clicked. + /// The builder that can be used to further build the button. + public NotificationButtonBuilder WithCommand(ICommand command) + { + _action = null; + _command = command; + return this; + } + + /// + /// Changes parameter of the command that is called when the button is clicked. + /// + /// The parameter of the command to call when the button is clicked. + /// The builder that can be used to further build the button. + public NotificationButtonBuilder WithCommandParameter(object? commandParameter) + { + _commandParameter = commandParameter; + return this; + } + + internal IControl Build() + { + if (_action != null) + return new Button {Content = _text, Command = ReactiveCommand.Create(() => _action())}; + if (_command != null) + return new Button {Content = _text, Command = _command, CommandParameter = _commandParameter}; + return new Button {Content = _text}; + } +} + +/// +/// Represents a severity of a notification. +/// +public enum NotificationSeverity { /// - /// Represents a builder that can be used to create notifications. + /// A severity for informational messages. /// - public class NotificationBuilder - { - private readonly InfoBar _infoBar; - private readonly Window _parent; - private TimeSpan _timeout = TimeSpan.FromSeconds(5); - - /// - /// Creates a new instance of the class. - /// - /// The parent window that will host the notification. - public NotificationBuilder(Window parent) - { - _parent = parent; - _infoBar = new InfoBar - { - Classes = Classes.Parse("notification-info-bar"), - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Right - }; - } - - /// - /// Changes the title of the notification. - /// - /// The new title. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder WithTitle(string? title) - { - _infoBar.Title = title; - return this; - } - - /// - /// Changes the message of the notification. - /// - /// The new message. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder WithMessage(string? content) - { - _infoBar.Message = content; - return this; - } - - /// - /// Changes the timeout of the notification after which it disappears automatically. - /// - /// The timeout of the notification after which it disappears automatically. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder WithTimeout(TimeSpan timeout) - { - _timeout = timeout; - return this; - } - - /// - /// Changes the vertical position of the notification inside the parent window. - /// - /// The vertical position of the notification inside the parent window. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder WithVerticalPosition(VerticalAlignment position) - { - _infoBar.VerticalAlignment = position; - return this; - } - - /// - /// Changes the horizontal position of the notification inside the parent window. - /// - /// The horizontal position of the notification inside the parent window. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder WithHorizontalPosition(HorizontalAlignment position) - { - _infoBar.HorizontalAlignment = position; - return this; - } - - /// - /// Changes the severity (color) of the notification. - /// - /// The severity (color) of the notification. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder WithSeverity(NotificationSeverity severity) - { - _infoBar.Severity = (InfoBarSeverity) severity; - return this; - } - - /// - /// Changes the action button of the notification. - /// - /// An action to configure the button. - /// The notification builder that can be used to further build the notification. - public NotificationBuilder HavingButton(Action configure) - { - NotificationButtonBuilder builder = new(); - configure(builder); - _infoBar.ActionButton = builder.Build(); - - return this; - } - - /// - /// Shows the notification. - /// - public void Show() - { - if (_parent.Content is not Panel panel) - return; - - Dispatcher.UIThread.Post(() => - { - panel.Children.Add(_infoBar); - _infoBar.Closed += InfoBarOnClosed; - _infoBar.IsOpen = true; - }); - - Task.Run(async () => - { - await Task.Delay(_timeout); - Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false); - }); - } - - private void InfoBarOnClosed(InfoBar sender, InfoBarClosedEventArgs args) - { - _infoBar.Closed -= InfoBarOnClosed; - if (_parent.Content is not Panel panel) - return; - - panel.Children.Remove(_infoBar); - } - } + Informational, /// - /// Represents a builder that can be used to create buttons inside notifications. + /// A severity for success messages. /// - public class NotificationButtonBuilder - { - private Action? _action; - private ICommand? _command; - private string _text = "Text"; - private object? _commandParameter; - - /// - /// Changes text message of the button. - /// - /// The new text. - /// The notification builder that can be used to further build the button. - public NotificationButtonBuilder WithText(string text) - { - _text = text; - return this; - } - - /// - /// Changes action that is called when the button is clicked. - /// - /// The action to call when the button is clicked. - /// The builder that can be used to further build the button. - public NotificationButtonBuilder WithAction(Action action) - { - _command = null; - _action = action; - return this; - } - - /// - /// Changes command that is called when the button is clicked. - /// - /// The command to call when the button is clicked. - /// The builder that can be used to further build the button. - public NotificationButtonBuilder WithCommand(ICommand command) - { - _action = null; - _command = command; - return this; - } - - /// - /// Changes parameter of the command that is called when the button is clicked. - /// - /// The parameter of the command to call when the button is clicked. - /// The builder that can be used to further build the button. - public NotificationButtonBuilder WithCommandParameter(object? commandParameter) - { - _commandParameter = commandParameter; - return this; - } - - internal IControl Build() - { - if (_action != null) - return new Button {Content = _text, Command = ReactiveCommand.Create(() => _action())}; - if (_command != null) - return new Button {Content = _text, Command = _command, CommandParameter = _commandParameter}; - return new Button {Content = _text}; - } - } + Success, /// - /// Represents a severity of a notification. + /// A severity for warning messages. /// - public enum NotificationSeverity - { - /// - /// A severity for informational messages. - /// - Informational, + Warning, - /// - /// A severity for success messages. - /// - Success, - - /// - /// A severity for warning messages. - /// - Warning, - - /// - /// A severity for error messages. - /// - Error - } + /// + /// A severity for error messages. + /// + Error } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/RenameProfileElement.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/RenameProfileElement.cs new file mode 100644 index 000000000..709b21665 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/RenameProfileElement.cs @@ -0,0 +1,51 @@ +using System; +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to rename a profile element. +/// +public class RenameProfileElement : IProfileEditorCommand +{ + private readonly string? _name; + private readonly string? _originalName; + private readonly ProfileElement _subject; + + /// + /// Creates a new instance of the class. + /// + public RenameProfileElement(ProfileElement subject, string? name) + { + _subject = subject; + _name = name; + _originalName = subject.Name; + + DisplayName = subject switch + { + Layer => "Rename layer", + Folder => "Rename folder", + _ => throw new ArgumentException("Type of subject is not supported") + }; + } + + + #region Implementation of IProfileEditorCommand + + /// + public string DisplayName { get; } + + /// + public void Execute() + { + _subject.Name = _name; + } + + /// + public void Undo() + { + _subject.Name = _originalName; + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorHistory.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorHistory.cs index be9bd2254..f09f471f4 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorHistory.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorHistory.cs @@ -30,8 +30,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor public IObservable CanRedo => _canRedo.AsObservable().DistinctUntilChanged(); public ReactiveCommand Execute { get; } - public ReactiveCommand Undo { get; } - public ReactiveCommand Redo { get; } + public ReactiveCommand Undo { get; } + public ReactiveCommand Redo { get; } public void Clear() { @@ -67,24 +67,28 @@ namespace Artemis.UI.Shared.Services.ProfileEditor _undoCommands.Clear(); } - private void ExecuteUndo() + private IProfileEditorCommand? ExecuteUndo() { if (!_undoCommands.TryPop(out IProfileEditorCommand? command)) - return; + return null; command.Undo(); _redoCommands.Push(command); UpdateSubjects(); + + return command; } - private void ExecuteRedo() + private IProfileEditorCommand? ExecuteRedo() { if (!_redoCommands.TryPop(out IProfileEditorCommand? command)) - return; + return null; command.Execute(); _undoCommands.Push(command); UpdateSubjects(); + + return command; } private void UpdateSubjects() diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index c82ae0597..b78f1b9de 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -22,8 +22,8 @@ internal class ProfileEditorService : IProfileEditorService { _profileService = profileService; _windowService = windowService; - ProfileConfiguration = _profileConfigurationSubject.AsObservable().DistinctUntilChanged(); - ProfileElement = _profileElementSubject.AsObservable().DistinctUntilChanged(); + ProfileConfiguration = _profileConfigurationSubject.AsObservable(); + ProfileElement = _profileElementSubject.AsObservable(); History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory)); } diff --git a/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml b/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml index 5bcd365a3..b359ef18f 100644 --- a/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml +++ b/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml @@ -25,6 +25,7 @@ + diff --git a/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml b/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml new file mode 100644 index 000000000..acc19236b --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs index 84456eac9..8234d6a5f 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs @@ -1,6 +1,9 @@ using System; +using System.Reactive; using System.Reactive.Disposables; +using System.Reactive.Linq; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.ProfileEditor; using ReactiveUI; @@ -8,13 +11,42 @@ namespace Artemis.UI.Screens.ProfileEditor.MenuBar { public class MenuBarViewModel : ActivatableViewModelBase { + private readonly INotificationService _notificationService; private ProfileEditorHistory? _history; + private Action? _lastMessage; - public MenuBarViewModel(IProfileEditorService profileEditorService) + public MenuBarViewModel(IProfileEditorService profileEditorService, INotificationService notificationService) { + _notificationService = notificationService; this.WhenActivated(d => profileEditorService.History.Subscribe(history => History = history).DisposeWith(d)); + this.WhenAnyValue(x => x.History) + .Select(h => h?.Undo ?? Observable.Never()) + .Switch() + .Subscribe(DisplayUndo); + this.WhenAnyValue(x => x.History) + .Select(h => h?.Redo ?? Observable.Never()) + .Switch() + .Subscribe(DisplayRedo); } + private void DisplayUndo(IProfileEditorCommand? command) + { + if (command == null || History == null) + return; + + _lastMessage?.Invoke(); + _lastMessage = _notificationService.CreateNotification().WithMessage($"Undid '{command.DisplayName}'.").HavingButton(b => b.WithText("Redo").WithCommand(History.Redo)).Show(); + } + + private void DisplayRedo(IProfileEditorCommand? command) + { + if (command == null || History == null) + return; + + _lastMessage?.Invoke(); + _notificationService.CreateNotification().WithMessage($"Redid '{command.DisplayName}'.").HavingButton(b => b.WithText("Undo").WithCommand(History.Undo)).Show(); ; + } + public ProfileEditorHistory? History { get => _history; diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml index af78df0b0..9f2443089 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml @@ -14,19 +14,33 @@ + + + - + + + + + + + + - + diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs index 4a9482d73..8926536a3 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs @@ -1,3 +1,4 @@ +using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; @@ -14,5 +15,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree { AvaloniaXamlLoader.Load(this); } + + private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (ViewModel != null) + ViewModel.ProfileElementPropertyGroupViewModel.IsExpanded = !ViewModel.ProfileElementPropertyGroupViewModel.IsExpanded; + } } } diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml index 34bb3b41a..393a3f197 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml @@ -25,7 +25,15 @@ Kind="FolderOpen" Margin="0 0 5 0" IsVisible="{Binding IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" /> - + + ViewModel?.Rename.Subscribe(_ => + { + this.Get("Input").Focus(); + this.Get("Input").SelectAll(); + })); + } + + 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(); } } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs index 5055f6d35..aee6dbd16 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs @@ -14,5 +14,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree } public Folder Folder { get; } + + } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml index ed528d469..a76dc395c 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml @@ -16,7 +16,15 @@ - + + ViewModel?.Rename.Subscribe(_ => + { + this.Get("Input").Focus(); + this.Get("Input").SelectAll(); + })); + } + + 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(); } } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml index 192d579d3..95fcc9084 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml @@ -13,11 +13,11 @@ - - - - - + + + + + @@ -35,28 +35,28 @@ - + - + - + - + - + diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs index d4e730438..802c6a6e4 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reactive; using System.Reactive.Disposables; using Artemis.Core; using Artemis.UI.Ninject.Factories; @@ -41,8 +42,10 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree if (model?.ProfileElement is RenderProfileElement renderProfileElement) profileEditorService.ChangeCurrentProfileElement(renderProfileElement); }); - } + + } + public TreeItemViewModel? SelectedChild { get => _selectedChild; diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index e3662b8ca..5cc8c6db3 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -24,6 +24,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree private bool _isExpanded; private ProfileElement? _profileElement; private RenderProfileElement? _currentProfileElement; + private bool _renaming; + private string? _renameValue; protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory) @@ -51,6 +53,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, "New folder"), ProfileElement, 0)); }); + Rename = ReactiveCommand.Create(() => + { + Renaming = true; + RenameValue = ProfileElement?.Name; + }); + this.WhenActivated(d => { _profileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d); @@ -71,11 +79,24 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree set => this.RaiseAndSetIfChanged(ref _isExpanded, value); } + public bool Renaming + { + get => _renaming; + set => this.RaiseAndSetIfChanged(ref _renaming, value); + } + public TreeItemViewModel? Parent { get; set; } public ObservableCollection Children { get; } = new(); public ReactiveCommand AddLayer { get; } public ReactiveCommand AddFolder { get; } + public ReactiveCommand Rename { get; } + + public string? RenameValue + { + get => _renameValue; + set => this.RaiseAndSetIfChanged(ref _renameValue, value); + } public async Task ShowBrokenStateExceptions() { @@ -93,6 +114,23 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree } } + public void SubmitRename() + { + if (ProfileElement == null) + { + Renaming = false; + return; + } + + _profileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue)); + Renaming = false; + } + + public void CancelRename() + { + Renaming = false; + } + protected void SubscribeToProfileElement(CompositeDisposable d) { if (ProfileElement == null)