mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'development'
This commit is contained in:
commit
b1e39dbd39
@ -4,8 +4,8 @@
|
|||||||
"src": [
|
"src": [
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"Artemis.Core/bin/net6.0/Artemis.Core.dll",
|
"Artemis.Core/bin/net7.0/Artemis.Core.dll",
|
||||||
"Artemis.UI.Shared/bin/net6.0/Artemis.UI.Shared.dll",
|
"Artemis.UI.Shared/bin/net7.0/Artemis.UI.Shared.dll",
|
||||||
],
|
],
|
||||||
"src": "../../src"
|
"src": "../../src"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,9 +43,9 @@
|
|||||||
<PackageReference Include="LiteDB" Version="5.0.16" />
|
<PackageReference Include="LiteDB" Version="5.0.16" />
|
||||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.29" />
|
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.45" />
|
||||||
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.29" />
|
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.45" />
|
||||||
<PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.29" />
|
<PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.45" />
|
||||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
||||||
|
|||||||
@ -28,6 +28,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
private string _version = null!;
|
private string _version = null!;
|
||||||
private Uri? _website;
|
private Uri? _website;
|
||||||
private Uri? _helpPage;
|
private Uri? _helpPage;
|
||||||
|
private bool _hotReloadSupported;
|
||||||
|
|
||||||
internal PluginInfo()
|
internal PluginInfo()
|
||||||
{
|
{
|
||||||
@ -156,6 +157,17 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
internal set => SetAndNotify(ref _requiresAdmin, value);
|
internal set => SetAndNotify(ref _requiresAdmin, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether hot reloading this plugin is supported
|
||||||
|
/// </summary>
|
||||||
|
[DefaultValue(true)]
|
||||||
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||||
|
public bool HotReloadSupported
|
||||||
|
{
|
||||||
|
get => _hotReloadSupported;
|
||||||
|
set => SetAndNotify(ref _hotReloadSupported, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets
|
/// Gets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core.DeviceProviders;
|
using Artemis.Core.DeviceProviders;
|
||||||
using Artemis.Core.DryIoc;
|
using Artemis.Core.DryIoc;
|
||||||
@ -30,6 +31,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
private readonly IPluginRepository _pluginRepository;
|
private readonly IPluginRepository _pluginRepository;
|
||||||
private readonly List<Plugin> _plugins;
|
private readonly List<Plugin> _plugins;
|
||||||
private readonly IQueuedActionRepository _queuedActionRepository;
|
private readonly IQueuedActionRepository _queuedActionRepository;
|
||||||
|
private FileSystemWatcher _hotReloadWatcher;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private bool _isElevated;
|
private bool _isElevated;
|
||||||
|
|
||||||
@ -43,6 +45,8 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
_plugins = new List<Plugin>();
|
_plugins = new List<Plugin>();
|
||||||
|
|
||||||
ProcessPluginDeletionQueue();
|
ProcessPluginDeletionQueue();
|
||||||
|
|
||||||
|
StartHotReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
||||||
@ -218,6 +222,14 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Plugin? GetPluginByDirectory(DirectoryInfo directory)
|
||||||
|
{
|
||||||
|
lock (_plugins)
|
||||||
|
{
|
||||||
|
return _plugins.FirstOrDefault(p => p.Directory.FullName == directory.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Disposal happens manually before container disposal but the container doesn't know that so a 2nd call will be made
|
// 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
|
#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
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -17,14 +17,17 @@ internal class WebServerService : IWebServerService, IDisposable
|
|||||||
{
|
{
|
||||||
private readonly List<WebApiControllerRegistration> _controllers;
|
private readonly List<WebApiControllerRegistration> _controllers;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
private readonly List<WebModuleRegistration> _modules;
|
private readonly List<WebModuleRegistration> _modules;
|
||||||
private readonly PluginSetting<bool> _webServerEnabledSetting;
|
private readonly PluginSetting<bool> _webServerEnabledSetting;
|
||||||
private readonly PluginSetting<int> _webServerPortSetting;
|
private readonly PluginSetting<int> _webServerPortSetting;
|
||||||
|
private readonly object _webserverLock = new();
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
|
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_coreService = coreService;
|
||||||
_controllers = new List<WebApiControllerRegistration>();
|
_controllers = new List<WebApiControllerRegistration>();
|
||||||
_modules = new List<WebModuleRegistration>();
|
_modules = new List<WebModuleRegistration>();
|
||||||
|
|
||||||
@ -35,7 +38,10 @@ internal class WebServerService : IWebServerService, IDisposable
|
|||||||
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
|
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
|
||||||
|
|
||||||
PluginsModule = new PluginsModule("/plugins");
|
PluginsModule = new PluginsModule("/plugins");
|
||||||
StartWebServer();
|
if (coreService.IsInitialized)
|
||||||
|
StartWebServer();
|
||||||
|
else
|
||||||
|
coreService.Initialized += (_, _) => StartWebServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler? WebServerStopped;
|
public event EventHandler? WebServerStopped;
|
||||||
@ -144,21 +150,28 @@ internal class WebServerService : IWebServerService, IDisposable
|
|||||||
|
|
||||||
private void StartWebServer()
|
private void StartWebServer()
|
||||||
{
|
{
|
||||||
Server = CreateWebServer();
|
lock (_webserverLock)
|
||||||
|
|
||||||
if (!_webServerEnabledSetting.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Constants.StartupArguments.Contains("--disable-webserver"))
|
|
||||||
{
|
{
|
||||||
_logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver");
|
// Don't create the webserver until after the core service is initialized, this avoids lots of useless re-creates during initialize
|
||||||
return;
|
if (!_coreService.IsInitialized)
|
||||||
}
|
return;
|
||||||
|
|
||||||
OnWebServerStarting();
|
if (!_webServerEnabledSetting.Value)
|
||||||
_cts = new CancellationTokenSource();
|
return;
|
||||||
Server.Start(_cts.Token);
|
|
||||||
OnWebServerStarted();
|
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
|
#endregion
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.0-preview3" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.0-preview3" />
|
||||||
<PackageReference Include="ReactiveUI" Version="18.4.26" />
|
<PackageReference Include="ReactiveUI" Version="18.4.26" />
|
||||||
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
|
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
|
||||||
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.29" />
|
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.45" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindow/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindow/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@ -61,6 +61,9 @@ internal class DeviceVisualizerLed
|
|||||||
|
|
||||||
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
|
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
|
||||||
{
|
{
|
||||||
|
if (DisplayGeometry == null)
|
||||||
|
return;
|
||||||
|
|
||||||
byte r = Led.RgbLed.Color.GetR();
|
byte r = Led.RgbLed.Color.GetR();
|
||||||
byte g = Led.RgbLed.Color.GetG();
|
byte g = Led.RgbLed.Color.GetG();
|
||||||
byte b = Led.RgbLed.Color.GetB();
|
byte b = Led.RgbLed.Color.GetB();
|
||||||
@ -94,13 +97,13 @@ internal class DeviceVisualizerLed
|
|||||||
switch (Led.RgbLed.Shape)
|
switch (Led.RgbLed.Shape)
|
||||||
{
|
{
|
||||||
case Shape.Custom:
|
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);
|
CreateCustomGeometry(2.0);
|
||||||
else
|
else
|
||||||
CreateCustomGeometry(1.0);
|
CreateCustomGeometry(1.0);
|
||||||
break;
|
break;
|
||||||
case Shape.Rectangle:
|
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();
|
CreateKeyCapGeometry();
|
||||||
else
|
else
|
||||||
CreateRectangleGeometry();
|
CreateRectangleGeometry();
|
||||||
@ -132,6 +135,9 @@ internal class DeviceVisualizerLed
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (Led.RgbLed.ShapeData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
double width = Led.RgbLed.Size.Width - deflateAmount;
|
double width = Led.RgbLed.Size.Width - deflateAmount;
|
||||||
double height = Led.RgbLed.Size.Height - deflateAmount;
|
double height = Led.RgbLed.Size.Height - deflateAmount;
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -33,8 +33,8 @@
|
|||||||
<PackageReference Include="Octopus.Octodiff" Version="2.0.261" />
|
<PackageReference Include="Octopus.Octodiff" Version="2.0.261" />
|
||||||
<PackageReference Include="ReactiveUI" Version="18.4.26" />
|
<PackageReference Include="ReactiveUI" Version="18.4.26" />
|
||||||
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
|
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
|
||||||
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.29" />
|
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.45" />
|
||||||
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.29" />
|
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.45" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
<PackageReference Include="Splat.DryIoc" Version="14.6.8" />
|
<PackageReference Include="Splat.DryIoc" Version="14.6.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
|||||||
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
|
|||||||
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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));
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -26,5 +26,4 @@ public partial class LayerEffectRenameView : ReactiveUserControl<LayerEffectRena
|
|||||||
NameTextBox.SelectAll();
|
NameTextBox.SelectAll();
|
||||||
NameTextBox.Focus();
|
NameTextBox.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user