diff --git a/docfx/docfx_project/docfx.json b/docfx/docfx_project/docfx.json
index dc1d65129..525c27ace 100644
--- a/docfx/docfx_project/docfx.json
+++ b/docfx/docfx_project/docfx.json
@@ -4,8 +4,8 @@
"src": [
{
"files": [
- "Artemis.Core/bin/net6.0/Artemis.Core.dll",
- "Artemis.UI.Shared/bin/net6.0/Artemis.UI.Shared.dll",
+ "Artemis.Core/bin/net7.0/Artemis.Core.dll",
+ "Artemis.UI.Shared/bin/net7.0/Artemis.UI.Shared.dll",
],
"src": "../../src"
}
diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj
index fe9420a6a..f4e4d85f2 100644
--- a/src/Artemis.Core/Artemis.Core.csproj
+++ b/src/Artemis.Core/Artemis.Core.csproj
@@ -43,9 +43,9 @@
-
-
-
+
+
+
diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs
index e8c3f5d50..1eebd03c6 100644
--- a/src/Artemis.Core/Plugins/PluginInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginInfo.cs
@@ -28,6 +28,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
private string _version = null!;
private Uri? _website;
private Uri? _helpPage;
+ private bool _hotReloadSupported;
internal PluginInfo()
{
@@ -156,6 +157,17 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
internal set => SetAndNotify(ref _requiresAdmin, value);
}
+ ///
+ /// Gets or sets a boolean indicating whether hot reloading this plugin is supported
+ ///
+ [DefaultValue(true)]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ public bool HotReloadSupported
+ {
+ get => _hotReloadSupported;
+ set => SetAndNotify(ref _hotReloadSupported, value);
+ }
+
///
/// Gets
///
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index c03aec7bb..fb304baf5 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
+using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
using Artemis.Core.DryIoc;
@@ -30,6 +31,7 @@ internal class PluginManagementService : IPluginManagementService
private readonly IPluginRepository _pluginRepository;
private readonly List _plugins;
private readonly IQueuedActionRepository _queuedActionRepository;
+ private FileSystemWatcher _hotReloadWatcher;
private bool _disposed;
private bool _isElevated;
@@ -43,6 +45,8 @@ internal class PluginManagementService : IPluginManagementService
_plugins = new List();
ProcessPluginDeletionQueue();
+
+ StartHotReload();
}
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
@@ -218,6 +222,14 @@ internal class PluginManagementService : IPluginManagementService
return null;
}
+ private Plugin? GetPluginByDirectory(DirectoryInfo directory)
+ {
+ lock (_plugins)
+ {
+ return _plugins.FirstOrDefault(p => p.Directory.FullName == directory.FullName);
+ }
+ }
+
public void Dispose()
{
// Disposal happens manually before container disposal but the container doesn't know that so a 2nd call will be made
@@ -912,6 +924,67 @@ internal class PluginManagementService : IPluginManagementService
}
#endregion
+
+ #region Hot Reload
+
+ private void StartHotReload()
+ {
+ // Watch for changes in the plugin directory, "plugin.json".
+ // If this file is changed, reload the plugin.
+ _hotReloadWatcher = new FileSystemWatcher(Constants.PluginsFolder, "plugin.json");
+ _hotReloadWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.FileName;
+ _hotReloadWatcher.Created += FileSystemWatcherOnCreated;
+ _hotReloadWatcher.Error += FileSystemWatcherOnError;
+ _hotReloadWatcher.IncludeSubdirectories = true;
+ _hotReloadWatcher.EnableRaisingEvents = true;
+ }
+
+ private void FileSystemWatcherOnError(object sender, ErrorEventArgs e)
+ {
+ _logger.Error(e.GetException(), "File system watcher error");
+ }
+
+ private void FileSystemWatcherOnCreated(object sender, FileSystemEventArgs e)
+ {
+ string? pluginPath = Path.GetDirectoryName(e.FullPath);
+ if (pluginPath == null)
+ {
+ _logger.Warning("Plugin change detected, but could not get plugin directory. {fullPath}", e.FullPath);
+ return;
+ }
+
+ DirectoryInfo pluginDirectory = new(pluginPath);
+ Plugin? plugin = GetPluginByDirectory(pluginDirectory);
+
+ if (plugin == null)
+ {
+ _logger.Warning("Plugin change detected, but could not find plugin. {fullPath}", e.FullPath);
+ return;
+ }
+
+ if (!plugin.Info.HotReloadSupported)
+ {
+ _logger.Information("Plugin change detected, but hot reload not supported. {pluginName}", plugin.Info.Name);
+ return;
+ }
+
+ _logger.Information("Plugin change detected, reloading. {pluginName}", plugin.Info.Name);
+ bool wasEnabled = plugin.IsEnabled;
+
+ UnloadPlugin(plugin);
+ Thread.Sleep(500);
+ Plugin? loadedPlugin = LoadPlugin(pluginDirectory);
+
+ if (loadedPlugin == null)
+ return;
+
+ if (wasEnabled)
+ EnablePlugin(loadedPlugin, true, false);
+
+ _logger.Information("Plugin reloaded. {fullPath}", e.FullPath);
+ }
+
+ #endregion
}
///
diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs
index 83d269fc4..e5030c9b4 100644
--- a/src/Artemis.Core/Services/WebServer/WebServerService.cs
+++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs
@@ -17,14 +17,17 @@ internal class WebServerService : IWebServerService, IDisposable
{
private readonly List _controllers;
private readonly ILogger _logger;
+ private readonly ICoreService _coreService;
private readonly List _modules;
private readonly PluginSetting _webServerEnabledSetting;
private readonly PluginSetting _webServerPortSetting;
+ private readonly object _webserverLock = new();
private CancellationTokenSource? _cts;
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{
_logger = logger;
+ _coreService = coreService;
_controllers = new List();
_modules = new List();
@@ -35,7 +38,10 @@ internal class WebServerService : IWebServerService, IDisposable
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
PluginsModule = new PluginsModule("/plugins");
- StartWebServer();
+ if (coreService.IsInitialized)
+ StartWebServer();
+ else
+ coreService.Initialized += (_, _) => StartWebServer();
}
public event EventHandler? WebServerStopped;
@@ -144,21 +150,28 @@ internal class WebServerService : IWebServerService, IDisposable
private void StartWebServer()
{
- Server = CreateWebServer();
-
- if (!_webServerEnabledSetting.Value)
- return;
-
- if (Constants.StartupArguments.Contains("--disable-webserver"))
+ lock (_webserverLock)
{
- _logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver");
- return;
- }
+ // Don't create the webserver until after the core service is initialized, this avoids lots of useless re-creates during initialize
+ if (!_coreService.IsInitialized)
+ return;
- OnWebServerStarting();
- _cts = new CancellationTokenSource();
- Server.Start(_cts.Token);
- OnWebServerStarted();
+ if (!_webServerEnabledSetting.Value)
+ return;
+
+ Server = CreateWebServer();
+
+ if (Constants.StartupArguments.Contains("--disable-webserver"))
+ {
+ _logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver");
+ return;
+ }
+
+ OnWebServerStarting();
+ _cts = new CancellationTokenSource();
+ Server.Start(_cts.Token);
+ OnWebServerStarted();
+ }
}
#endregion
diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
index 4ae1e9f7c..35cf357d8 100644
--- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
+++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
index 4dc50ad43..849012cda 100644
--- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
+++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
@@ -1,5 +1,5 @@
-
+
+ True
True
True
True
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
index 30bb72b76..5164d5844 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
@@ -61,6 +61,9 @@ internal class DeviceVisualizerLed
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
{
+ if (DisplayGeometry == null)
+ return;
+
byte r = Led.RgbLed.Color.GetR();
byte g = Led.RgbLed.Color.GetG();
byte b = Led.RgbLed.Color.GetB();
@@ -94,13 +97,13 @@ internal class DeviceVisualizerLed
switch (Led.RgbLed.Shape)
{
case Shape.Custom:
- if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
+ if (Led.RgbLed.Device.DeviceInfo.DeviceType is RGBDeviceType.Keyboard or RGBDeviceType.Keypad)
CreateCustomGeometry(2.0);
else
CreateCustomGeometry(1.0);
break;
case Shape.Rectangle:
- if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
+ if (Led.RgbLed.Device.DeviceInfo.DeviceType is RGBDeviceType.Keyboard or RGBDeviceType.Keypad)
CreateKeyCapGeometry();
else
CreateRectangleGeometry();
@@ -132,6 +135,9 @@ internal class DeviceVisualizerLed
{
try
{
+ if (Led.RgbLed.ShapeData == null)
+ return;
+
double width = Led.RgbLed.Size.Width - deflateAmount;
double height = Led.RgbLed.Size.Height - deflateAmount;
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
index 82a217a99..e766659c6 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
@@ -51,11 +51,6 @@ public interface IProfileEditorService : IArtemisSharedUIService
///
IObservable SuspendedEditing { get; }
- ///
- /// Gets an observable of the suspended keybindings state.
- ///
- IObservable SuspendedKeybindings { get; }
-
///
/// Gets an observable of the suspended keybindings state.
///
@@ -102,12 +97,6 @@ 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);
-
///
/// Changes the current focus mode.
///
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index d02f99dcc..270798b89 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -31,7 +31,6 @@ internal class ProfileEditorService : IProfileEditorService
private readonly IRgbService _rgbService;
private readonly SourceList _selectedKeyframes;
private readonly BehaviorSubject _suspendedEditingSubject = new(false);
- private readonly BehaviorSubject _suspendedKeybindingsSubject = new(false);
private readonly BehaviorSubject _timeSubject = new(TimeSpan.Zero);
private readonly IWindowService _windowService;
private ProfileEditorCommandScope? _profileEditorHistoryScope;
@@ -61,7 +60,6 @@ internal class ProfileEditorService : IProfileEditorService
Time = _timeSubject.AsObservable();
Playing = _playingSubject.AsObservable();
SuspendedEditing = _suspendedEditingSubject.AsObservable();
- SuspendedKeybindings = _suspendedKeybindingsSubject.AsObservable();
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
FocusMode = _focusModeSubject.AsObservable();
SelectedKeyframes = selectedKeyframes;
@@ -210,7 +208,6 @@ internal class ProfileEditorService : IProfileEditorService
_profileConfigurationSubject.OnNext(profileConfiguration);
ChangeTime(TimeSpan.Zero);
- ChangeSuspendedKeybindings(false);
}
public void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement)
@@ -255,15 +252,7 @@ internal class ProfileEditorService : IProfileEditorService
ApplyFocusMode();
}
-
- public void ChangeSuspendedKeybindings(bool suspend)
- {
- if (_suspendedKeybindingsSubject.Value == suspend)
- return;
-
- _suspendedKeybindingsSubject.OnNext(suspend);
- }
-
+
public void ChangeFocusMode(ProfileEditorFocusMode focusMode)
{
if (_focusModeSubject.Value == focusMode)
diff --git a/src/Artemis.UI.Shared/Utilities.cs b/src/Artemis.UI.Shared/Utilities.cs
index 35abef7db..9428b183b 100644
--- a/src/Artemis.UI.Shared/Utilities.cs
+++ b/src/Artemis.UI.Shared/Utilities.cs
@@ -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;
-internal static class UI
+///
+/// Static UI helpers.
+///
+public static class UI
{
+ private static readonly BehaviorSubject KeyBindingsEnabledSubject = new(false);
+
+ static UI()
+ {
+ if (KeyboardDevice.Instance != null)
+ KeyboardDevice.Instance.PropertyChanged += InstanceOnPropertyChanged;
+ }
+
+ ///
+ /// Gets the current IoC locator.
+ ///
public static IContainer Locator { get; set; } = null!;
+
+ ///
+ /// Gets a boolean indicating whether hotkeys are to be disabled.
+ ///
+ public static IObservable 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);
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj
index a1252420a..4e5a5ccde 100644
--- a/src/Artemis.UI/Artemis.UI.csproj
+++ b/src/Artemis.UI/Artemis.UI.csproj
@@ -33,8 +33,8 @@
-
-
+
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs
index b7cf96087..4d7247986 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs
@@ -58,7 +58,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
_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);
_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);
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs
index 376ddb92a..80039a4ba 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs
@@ -52,7 +52,7 @@ 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);
- _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;
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update);
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameView.axaml
new file mode 100644
index 000000000..d9a846e3e
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameView.axaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameView.axaml.cs
new file mode 100644
index 000000000..bdcc4c91f
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameView.axaml.cs
@@ -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
+{
+ 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();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameViewModel.cs
new file mode 100644
index 000000000..26a07ff15
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ContentDialogs/ProfileElementRenameViewModel.cs
@@ -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 Enter { get; }
+ public ReactiveCommand Confirm { get; }
+
+ private void ExecuteConfirm()
+ {
+ if (ProfileElementName == null)
+ return;
+
+ _profileEditorService.ExecuteCommand(new RenameProfileElement(_profileElement, ProfileElementName));
+ ContentDialog?.Hide(ContentDialogResult.Primary);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml
index 60f0456da..aa95e39f5 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml
@@ -25,15 +25,8 @@
Kind="FolderOpen"
Margin="0 0 5 0"
IsVisible="{Binding IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" />
-
-
+
+
- {
- 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();
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml
index 44eb5b0d0..5bd4a809b 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml
@@ -18,15 +18,8 @@
-
-
+
+
- {
- 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();
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs
index adbd6153d..7a331d321 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs
@@ -45,7 +45,7 @@ public class ProfileTreeViewModel : TreeItemViewModel
_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);
_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));
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs
index 1ff9b311a..9d995f005 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs
@@ -9,9 +9,11 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Extensions;
+using Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs;
using Artemis.UI.Shared;
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 Avalonia;
@@ -30,8 +32,6 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
private bool _isFlyoutOpen;
private ObservableAsPropertyHelper? _isFocused;
private ProfileElement? _profileElement;
- private string? _renameValue;
- private bool _renaming;
private TimeSpan _time;
protected TreeItemViewModel(TreeItemViewModel? parent,
@@ -50,7 +50,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
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);
Duplicate = ReactiveCommand.CreateFromTask(ExecuteDuplicate);
Copy = ReactiveCommand.CreateFromTask(ExecuteCopy);
@@ -96,12 +96,6 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _isFlyoutOpen, value);
}
- public bool Renaming
- {
- get => _renaming;
- set => RaiseAndSetIfChanged(ref _renaming, value);
- }
-
public bool CanPaste
{
get => _canPaste;
@@ -120,13 +114,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
public ReactiveCommand Paste { get; }
public ReactiveCommand Delete { get; }
public abstract bool SupportsChildren { get; }
-
- public string? RenameValue
- {
- get => _renameValue;
- set => RaiseAndSetIfChanged(ref _renameValue, value);
- }
-
+
public async Task ShowBrokenStateExceptions()
{
if (ProfileElement == null)
@@ -143,26 +131,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
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)
{
if (elementViewModel.Parent == this && Children.IndexOf(elementViewModel) == targetIndex)
@@ -250,11 +219,18 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
ProfileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement));
}
- private void ExecuteRename()
+ private async Task ExecuteRename()
{
- Renaming = true;
- RenameValue = ProfileElement?.Name;
- ProfileEditorService.ChangeSuspendedKeybindings(true);
+ if (ProfileElement == null)
+ return;
+
+ 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()
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml
index 744838e50..ba9da9a27 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml
@@ -8,7 +8,7 @@
x:DataType="contentDialogs:LayerEffectRenameViewModel">
-
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml.cs
index 19972807c..9f99d70b9 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml.cs
@@ -26,5 +26,4 @@ public partial class LayerEffectRenameView : ReactiveUserControl ContentDialog?.Hide(ContentDialogResult.Primary), Confirm.CanExecute);
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 Confirm { get; }
+ public ReactiveCommand Enter { get; }
private void ExecuteConfirm()
{
diff --git a/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml b/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml
index 351b50f79..a27bb9281 100644
--- a/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml
+++ b/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml
@@ -8,7 +8,7 @@
x:DataType="sidebar:SidebarCategoryEditViewModel">
-
+
diff --git a/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditViewModel.cs b/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditViewModel.cs
index 5096cc2d3..0884562ed 100644
--- a/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditViewModel.cs
+++ b/src/Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditViewModel.cs
@@ -24,6 +24,7 @@ public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
_categoryName = _category.Name;
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 => profileService.ProfileCategories.All(c => c.Name != categoryName?.Trim()), "You must specify a unique name");
}
@@ -35,6 +36,7 @@ public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
}
public ReactiveCommand Confirm { get; }
+ public ReactiveCommand Enter { get; }
private void ExecuteConfirm()
{
diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs
index 37348d46d..831c69b93 100644
--- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs
+++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs
@@ -27,6 +27,7 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private bool _pauseUpdate;
+ private ObservableAsPropertyHelper? _keyBindingsEnabled;
public NodeScriptWindowViewModel(NodeScript nodeScript,
INodeService nodeService,
@@ -49,7 +50,7 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
_windowService = windowService;
CreateNode = ReactiveCommand.Create(ExecuteCreateNode);
- AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange);
+ AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
Export = ReactiveCommand.CreateFromTask(ExecuteExport);
Import = ReactiveCommand.CreateFromTask(ExecuteImport);
@@ -64,6 +65,8 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
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);
// TODO: Remove in favor of saving each time a node editor command is executed
DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save);
@@ -89,7 +92,8 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
public ReactiveCommand AutoArrange { get; }
public ReactiveCommand Export { get; }
public ReactiveCommand Import { get; }
-
+ public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
+
public PluginSetting ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
public PluginSetting AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true);