From b4ab8f696952a2ac4f7f0f5ac60d1ed3a9751bf3 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 Feb 2021 23:40:30 +0100 Subject: [PATCH 01/21] Device debugger - Redesigned window Surface editor - Added option to disable color overlay --- .../Ninject/Factories/IVMFactory.cs | 5 +- .../Debug/Device/DeviceDebugView.xaml | 134 ++++++++ .../{ => Device}/DeviceDebugViewModel.cs | 20 +- .../Debug/Device/Tabs/DeviceLedsTabView.xaml | 34 ++ .../Device/Tabs/DeviceLedsTabViewModel.cs | 17 + .../Device/Tabs/DevicePropertiesTabView.xaml | 197 +++++++++++ .../Tabs/DevicePropertiesTabViewModel.cs | 16 + .../Settings/Debug/DeviceDebugView.xaml | 310 ------------------ .../Tabs/Devices/DeviceSettingsViewModel.cs | 2 +- .../SurfaceEditor/SurfaceEditorView.xaml | 12 + .../SurfaceEditor/SurfaceEditorViewModel.cs | 19 +- .../Visualization/ListDeviceView.xaml | 8 +- .../Visualization/ListDeviceViewModel.cs | 21 +- 13 files changed, 465 insertions(+), 330 deletions(-) create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml rename src/Artemis.UI/Screens/Settings/Debug/{ => Device}/DeviceDebugViewModel.cs (90%) create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 32daa2030..1fc61d300 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -15,6 +15,7 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Settings.Debug; +using Artemis.UI.Screens.Settings.Debug.Device.Tabs; using Artemis.UI.Screens.Settings.Tabs.Devices; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Screens.Shared; @@ -43,7 +44,9 @@ namespace Artemis.UI.Ninject.Factories public interface IDeviceDebugVmFactory : IVmFactory { - DeviceDebugViewModel Create(ArtemisDevice device); + DeviceDebugViewModel DeviceDebugViewModel(ArtemisDevice device); + DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device); + DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device); } public interface IProfileTreeVmFactory : IVmFactory diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml new file mode 100644 index 000000000..67230820c --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugViewModel.cs similarity index 90% rename from src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs rename to src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugViewModel.cs index 0eac26446..8bb4e6266 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugViewModel.cs @@ -2,9 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Xml.Serialization; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Screens.Shared; using Artemis.UI.Shared.Services; using Ookii.Dialogs.Wpf; using RGB.NET.Layout; @@ -14,24 +17,30 @@ using Stylet; namespace Artemis.UI.Screens.Settings.Debug { - public class DeviceDebugViewModel : Screen + public class DeviceDebugViewModel : Conductor.Collection.OneActive { private readonly IDeviceService _deviceService; private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; private ArtemisLed _selectedLed; - public DeviceDebugViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService) + public DeviceDebugViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) { _deviceService = deviceService; _rgbService = rgbService; _dialogService = dialogService; + Device = device; + PanZoomViewModel = new PanZoomViewModel(); + + Items.Add(factory.DevicePropertiesTabViewModel(device)); + Items.Add(factory.DeviceLedsTabViewModel(device)); + ActiveItem = Items.First(); } - public List SelectedLeds => SelectedLed != null ? new List {SelectedLed} : null; public ArtemisDevice Device { get; } - + public PanZoomViewModel PanZoomViewModel { get; } + public ArtemisLed SelectedLed { get => _selectedLed; @@ -41,6 +50,7 @@ namespace Artemis.UI.Screens.Settings.Debug NotifyOfPropertyChange(nameof(SelectedLeds)); } } + public List SelectedLeds => SelectedLed != null ? new List { SelectedLed } : null; public bool CanOpenImageDirectory => Device.Layout?.Image != null; @@ -135,7 +145,7 @@ namespace Artemis.UI.Screens.Settings.Debug foreach (ArtemisLedLayout ledLayout in Device.Layout.Leds) { - if (ledLayout.LayoutCustomLedData.LogicalLayouts == null) + if (ledLayout.LayoutCustomLedData.LogicalLayouts == null) continue; // Only the image of the current logical layout is available as an URI, iterate each layout and find the images manually diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml new file mode 100644 index 000000000..6c67d140b --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs new file mode 100644 index 000000000..c2339bd93 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs @@ -0,0 +1,17 @@ +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Debug.Device.Tabs +{ + public class DeviceLedsTabViewModel : Screen + { + + public DeviceLedsTabViewModel(ArtemisDevice device) + { + Device = device; + DisplayName = "LEDS"; + } + + public ArtemisDevice Device { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml new file mode 100644 index 000000000..a0b12086e --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + Device name + + + + + + + + + + + + + + + Manufacturer + + + + + + + + + + + + + + + Device type + + + + + + + + + + + + + + + Physical layout + + + + + + + + + + + + + + + Device image + + + + + + + + + + + + + + + Size (1px = 1mm) + + + + + + + + + + + + + + Location (1px = 1mm) + + + + + + + + + + + + + + + Rotation (degrees) + + + + + + + + + + + + + + + Logical layout + + + + + + + + + + + + + + + Layout file path + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs new file mode 100644 index 000000000..450ed4617 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -0,0 +1,16 @@ +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Debug.Device.Tabs +{ + public class DevicePropertiesTabViewModel : Screen + { + public DevicePropertiesTabViewModel(ArtemisDevice device) + { + Device = device; + DisplayName = "PROPERTIES"; + } + + public ArtemisDevice Device { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml deleted file mode 100644 index 80dc57da4..000000000 --- a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - In this window you can view detailed information of the device. - Please note that having this window open can have a performance impact on your system. - - - - - - - - - - - - - - - - - - - - - - - - Device name - - - - - - - - - - - - - - - Manufacturer - - - - - - - - - - - - - - - Device type - - - - - - - - - - - - - - - Physical layout - - - - - - - - - - - - - - - Device image - - - - - - - - - - - - - - - Size (1px = 1mm) - - - - - - - - - - - - - - Location (1px = 1mm) - - - - - - - - - - - - - - - Rotation (degrees) - - - - - - - - - - - - - - - Logical layout - - - - - - - - - - - - - - - Layout file path - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs index f9de3cfe3..0dfa5effc 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs @@ -62,7 +62,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices public void ShowDeviceDebugger() { - _windowManager.ShowWindow(_deviceDebugVmFactory.Create(Device)); + _windowManager.ShowWindow(_deviceDebugVmFactory.DeviceDebugViewModel(Device)); } public void OpenPluginDirectory() diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml index 6cbafa329..0101d01e4 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml @@ -201,6 +201,18 @@ + + + + + Show random device colors + + + + + + SETTINGS + + + + - Plugin enabled + + Plugin enabled + + _pluginManagementService.EnablePlugin(Plugin, true)); From 41b8038b74f54edfe71877cc5c9ff8a5f3a8ad6b Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 27 Feb 2021 23:26:56 +0100 Subject: [PATCH 08/21] Data model debugger - Fixed dictionaries causing the debugger to fail Data model debugger - Added realtime updating (can be disabled) --- .../Models/Profile/DataModel/DataModelPath.cs | 18 +++++- .../Debug/Tabs/DataModelDebugView.xaml | 15 +++++ .../Debug/Tabs/DataModelDebugViewModel.cs | 13 +++- src/Artemis.UI/Services/UpdateService.cs | 59 ++++++++++--------- 4 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 931ba1d02..9c5953749 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -213,9 +213,25 @@ namespace Artemis.Core Expression? expression = Expression.Convert(parameter, Target.GetType()); Expression? nullCondition = null; + MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!; foreach (DataModelPathSegment segment in _segments) { - BinaryExpression notNull = Expression.NotEqual(expression, Expression.Default(expression.Type)); + BinaryExpression notNull; + try + { + notNull = Expression.NotEqual(expression, Expression.Default(expression.Type)); + } + catch (InvalidOperationException) + { + notNull = Expression.NotEqual( + Expression.Call( + null, + equals, + Expression.Convert(expression, typeof(object)), + Expression.Convert(Expression.Default(expression.Type), typeof(object))), + Expression.Constant(true)); + } + nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull; expression = segment.Initialize(parameter, expression, nullCondition); if (expression == null) diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index fca0a0970..213cf4533 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -19,6 +19,10 @@ + + + + @@ -48,6 +52,17 @@ IsEnabled="{Binding IsModuleFilterEnabled}" SelectedItem="{Binding SelectedModule}" ItemsSource="{Binding Modules}" /> + + + Slow updates + + + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 442b0996d..e2b83d7d0 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -20,12 +20,13 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private DataModelPropertiesViewModel _mainDataModel; private string _propertySearch; private Module _selectedModule; + private bool _slowUpdates; public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) { _dataModelUIService = dataModelUIService; _pluginManagementService = pluginManagementService; - _updateTimer = new Timer(500); + _updateTimer = new Timer(25); DisplayName = "Data model"; Modules = new BindableCollection(); @@ -43,6 +44,16 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs set => SetAndNotify(ref _propertySearch, value); } + public bool SlowUpdates + { + get => _slowUpdates; + set + { + SetAndNotify(ref _slowUpdates, value); + _updateTimer.Interval = _slowUpdates ? 500 : 25; + } + } + public BindableCollection Modules { get; } public Module SelectedModule diff --git a/src/Artemis.UI/Services/UpdateService.cs b/src/Artemis.UI/Services/UpdateService.cs index 96fdcf661..ac00d5426 100644 --- a/src/Artemis.UI/Services/UpdateService.cs +++ b/src/Artemis.UI/Services/UpdateService.cs @@ -16,7 +16,6 @@ using Artemis.UI.Shared.Services; using Flurl; using Flurl.Http; using MaterialDesignThemes.Wpf; -using Newtonsoft.Json.Linq; using Serilog; using File = System.IO.File; @@ -27,8 +26,8 @@ namespace Artemis.UI.Services private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/"; private readonly PluginSetting _autoInstallUpdates; private readonly PluginSetting _checkForUpdates; - private readonly ILogger _logger; private readonly IDialogService _dialogService; + private readonly ILogger _logger; private readonly IMessageService _messageService; private readonly IWindowService _windowService; @@ -46,6 +45,32 @@ namespace Artemis.UI.Services _checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged; } + private async Task OfferUpdate(DevOpsBuild buildInfo) + { + await _dialogService.ShowDialog(new Dictionary {{"buildInfo", buildInfo}}); + } + + private async Task UpdateInstaller() + { + string downloadUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe"; + string installerDirectory = Path.Combine(Constants.ApplicationFolder, "Installer"); + string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe"); + + _logger.Information("UpdateInstaller: Downloading installer from {downloadUrl}", downloadUrl); + using HttpClient client = new(); + HttpResponseMessage httpResponseMessage = await client.GetAsync(downloadUrl); + if (!httpResponseMessage.IsSuccessStatusCode) + throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}"); + + _logger.Information("UpdateInstaller: Writing installer file to {installerPath}", installerPath); + if (File.Exists(installerPath)) + File.Delete(installerPath); + + Core.Utilities.CreateAccessibleDirectory(installerDirectory); + await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None); + await httpResponseMessage.Content.CopyToAsync(fs); + } + public async Task AutoUpdate() { if (!_checkForUpdates.Value) @@ -68,7 +93,9 @@ namespace Artemis.UI.Services return false; if (_windowService.IsMainWindowOpen) + { await OfferUpdate(buildInfo); + } else if (_autoInstallUpdates.Value) { // Lets go @@ -92,11 +119,6 @@ namespace Artemis.UI.Services return true; } - private async Task OfferUpdate(DevOpsBuild buildInfo) - { - await _dialogService.ShowDialog(new Dictionary {{"buildInfo", buildInfo}}); - } - public async Task IsUpdateAvailable() { DevOpsBuild buildInfo = await GetBuildInfo(1); @@ -114,7 +136,9 @@ namespace Artemis.UI.Services // Always update installer if it is missing ^^ if (!File.Exists(installerPath)) + { await UpdateInstaller(); + } // Compare the creation date of the installer with the build date and update if needed else { @@ -182,27 +206,6 @@ namespace Artemis.UI.Services .GetJsonAsync(); } - private async Task UpdateInstaller() - { - string downloadUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe"; - string installerDirectory = Path.Combine(Constants.ApplicationFolder, "Installer"); - string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe"); - - _logger.Information("UpdateInstaller: Downloading installer from {downloadUrl}", downloadUrl); - using HttpClient client = new(); - HttpResponseMessage httpResponseMessage = await client.GetAsync(downloadUrl); - if (!httpResponseMessage.IsSuccessStatusCode) - throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}"); - - _logger.Information("UpdateInstaller: Writing installer file to {installerPath}", installerPath); - if (File.Exists(installerPath)) - File.Delete(installerPath); - else if (!Directory.Exists(installerDirectory)) - Directory.CreateDirectory(installerDirectory); - await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None); - await httpResponseMessage.Content.CopyToAsync(fs); - } - #region Event handlers private void CheckForUpdatesOnSettingChanged(object sender, EventArgs e) From 5b00c8f524cdc4f0cfc73a7843afaa655e0f0ffe Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 28 Feb 2021 11:02:34 +0100 Subject: [PATCH 09/21] Readme - Expanded build instructions to include Artemis.Plugins Debugger - XAML cleanup --- README.md | 8 ++++++-- .../Settings/Debug/Tabs/DataModelDebugView.xaml | 14 ++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 42694ef1f..90d3634c5 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,20 @@ Artemis 1 is no longer supported and Artemis 2 is in active development. This en 1. Create a central folder like ```C:\Repos``` 2. Clone RGB.NET's [development branch](https://github.com/DarthAffe/RGB.NET/tree/Development) into ```\RGB.NET``` 3. Clone Artemis into ```\Artemis``` +4. Clone Artemis.Plugins [master branch](https://github.com/Artemis-RGB/Artemis.Plugins/tree/master) into ```\Artemis.Plugins``` 5. Open ```\RGB.NET\RGB.NET.sln``` and build with the default config -4. Open ```\Artemis\src\Artemis.sln``` -5. Restore Nuget packages +6. Open ```\Artemis\src\Artemis.sln``` and build as Debug +7. Open ```\Artemis.Plugins\src\Artemis.Plugins.sln``` and build as Debug +8. Restore Nuget packages ##### Alternatively in PowerShell ```powershell git clone https://github.com/DarthAffe/RGB.NET -b Development RGB.NET git clone https://github.com/Artemis-RGB/Artemis Artemis +git clone https://github.com/Artemis-RGB/Artemis.Plugins Artemis.Plugins dotnet build .\RGB.NET\RGB.NET.sln dotnet build .\Artemis\src\Artemis.sln +dotnet build .\Artemis.Plugins\src\Artemis.Plugins.sln ``` For an up-to-date overview of what's currently being worked on, see the [Projects](https://github.com/SpoinkyNL/Artemis/projects) page diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index 213cf4533..729c67a9a 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -6,10 +6,10 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs" xmlns:s="https://github.com/canton7/Stylet" xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes" - xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core" xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> @@ -33,16 +33,18 @@ - + - Filter module - Filter module + - Date: Sun, 28 Feb 2021 17:15:28 +0100 Subject: [PATCH 10/21] Core - Don't disable already disabled plugins on dispose --- src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs | 6 ------ src/Artemis.Core/Plugins/PluginFeature.cs | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs index dd3d7ad5b..949065503 100644 --- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs +++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs @@ -51,12 +51,6 @@ namespace Artemis.Core.DeviceProviders /// public bool CanDetectLogicalLayout { get; protected set; } - /// - public override void Disable() - { - // Does not happen with device providers, they require Artemis to restart - } - /// /// Loads a layout for the specified device and wraps it in an /// diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index b0e08554f..1eadf61aa 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -129,7 +129,8 @@ namespace Artemis.Core internal virtual void InternalDisable() { - Disable(); + if (IsEnabled) + Disable(); } #region IDisposable From 5135a8839b4a714676283a1d25a683123fe340b7 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 2 Mar 2021 00:07:54 +0100 Subject: [PATCH 11/21] Device properties - Reworked window and merged with the debug window --- .../Converters/UriToFileNameConverter.cs | 22 ++ .../Ninject/Factories/IVMFactory.cs | 6 +- .../Device/Tabs/DevicePropertiesTabView.xaml | 197 ---------------- .../Tabs/DevicePropertiesTabViewModel.cs | 16 -- .../DeviceDialogView.xaml} | 11 +- .../DeviceDialogViewModel.cs} | 10 +- .../Device/Tabs/DeviceInfoTabView.xaml | 121 ++++++++++ .../Device/Tabs/DeviceInfoTabViewModel.cs | 18 ++ .../Device/Tabs/DeviceLedsTabView.xaml | 33 +-- .../Device/Tabs/DeviceLedsTabViewModel.cs | 2 +- .../Device/Tabs/DevicePropertiesTabView.xaml | 189 +++++++++++++++ .../Tabs/DevicePropertiesTabViewModel.cs} | 124 +++++----- .../SurfaceDeviceConfigViewModelValidator.cs | 6 +- .../Tabs/Devices/DeviceSettingsView.xaml | 6 - .../Tabs/Devices/DeviceSettingsViewModel.cs | 15 +- .../Dialogs/SurfaceDeviceConfigView.xaml | 218 ------------------ .../SurfaceEditor/SurfaceEditorViewModel.cs | 24 +- 17 files changed, 458 insertions(+), 560 deletions(-) create mode 100644 src/Artemis.UI/Converters/UriToFileNameConverter.cs delete mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml delete mode 100644 src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs rename src/Artemis.UI/Screens/Settings/{Debug/Device/DeviceDebugView.xaml => Device/DeviceDialogView.xaml} (94%) rename src/Artemis.UI/Screens/Settings/{Debug/Device/DeviceDebugViewModel.cs => Device/DeviceDialogViewModel.cs} (93%) create mode 100644 src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs rename src/Artemis.UI/Screens/Settings/{Debug => }/Device/Tabs/DeviceLedsTabView.xaml (53%) rename src/Artemis.UI/Screens/Settings/{Debug => }/Device/Tabs/DeviceLedsTabViewModel.cs (83%) create mode 100644 src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml rename src/Artemis.UI/Screens/{SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs => Settings/Device/Tabs/DevicePropertiesTabViewModel.cs} (85%) rename src/Artemis.UI/Screens/{SurfaceEditor/Dialogs => Settings/Device/Tabs}/SurfaceDeviceConfigViewModelValidator.cs (73%) delete mode 100644 src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml diff --git a/src/Artemis.UI/Converters/UriToFileNameConverter.cs b/src/Artemis.UI/Converters/UriToFileNameConverter.cs new file mode 100644 index 000000000..435c1fec5 --- /dev/null +++ b/src/Artemis.UI/Converters/UriToFileNameConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.IO; +using System.Windows.Data; + +namespace Artemis.UI.Converters +{ + public class UriToFileNameConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Uri uri && uri.IsFile) + return Path.GetFileName(uri.LocalPath); + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 1fc61d300..8dd40960e 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -15,7 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Settings.Debug; -using Artemis.UI.Screens.Settings.Debug.Device.Tabs; +using Artemis.UI.Screens.Settings.Device; +using Artemis.UI.Screens.Settings.Device.Tabs; using Artemis.UI.Screens.Settings.Tabs.Devices; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Screens.Shared; @@ -44,8 +45,9 @@ namespace Artemis.UI.Ninject.Factories public interface IDeviceDebugVmFactory : IVmFactory { - DeviceDebugViewModel DeviceDebugViewModel(ArtemisDevice device); + DeviceDialogViewModel DeviceDialogViewModel(ArtemisDevice device); DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device); + DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device); DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device); } diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml deleted file mode 100644 index a0b12086e..000000000 --- a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabView.xaml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - Device name - - - - - - - - - - - - - - - Manufacturer - - - - - - - - - - - - - - - Device type - - - - - - - - - - - - - - - Physical layout - - - - - - - - - - - - - - - Device image - - - - - - - - - - - - - - - Size (1px = 1mm) - - - - - - - - - - - - - - Location (1px = 1mm) - - - - - - - - - - - - - - - Rotation (degrees) - - - - - - - - - - - - - - - Logical layout - - - - - - - - - - - - - - - Layout file path - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs deleted file mode 100644 index 450ed4617..000000000 --- a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DevicePropertiesTabViewModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Artemis.Core; -using Stylet; - -namespace Artemis.UI.Screens.Settings.Debug.Device.Tabs -{ - public class DevicePropertiesTabViewModel : Screen - { - public DevicePropertiesTabViewModel(ArtemisDevice device) - { - Device = device; - DisplayName = "PROPERTIES"; - } - - public ArtemisDevice Device { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml similarity index 94% rename from src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml rename to src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml index 67230820c..7b82c904e 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml @@ -1,4 +1,4 @@ - @@ -80,7 +79,7 @@ - + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs similarity index 93% rename from src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugViewModel.cs rename to src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index 8bb4e6266..97d3ce8fb 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Device/DeviceDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -13,18 +13,16 @@ using Ookii.Dialogs.Wpf; using RGB.NET.Layout; using Stylet; -// using PropertyChanged; - -namespace Artemis.UI.Screens.Settings.Debug +namespace Artemis.UI.Screens.Settings.Device { - public class DeviceDebugViewModel : Conductor.Collection.OneActive + public class DeviceDialogViewModel : Conductor.Collection.OneActive { private readonly IDeviceService _deviceService; private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; private ArtemisLed _selectedLed; - public DeviceDebugViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) + public DeviceDialogViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) { _deviceService = deviceService; _rgbService = rgbService; @@ -34,8 +32,10 @@ namespace Artemis.UI.Screens.Settings.Debug PanZoomViewModel = new PanZoomViewModel(); Items.Add(factory.DevicePropertiesTabViewModel(device)); + Items.Add(factory.DeviceInfoTabViewModel(device)); Items.Add(factory.DeviceLedsTabViewModel(device)); ActiveItem = Items.First(); + DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis"; } public ArtemisDevice Device { get; } diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml new file mode 100644 index 000000000..5b4ce48a4 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + Device name + + + + Manufacturer + + + + + Device type + + + + + Physical layout + + + + + + Size (1px = 1mm) + + + + Location (1px = 1mm) + + + + Rotation (degrees) + + + + + Logical layout + + + + + + + + + + + + + Layout file path + + + + + + + + + + + Image file path + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs new file mode 100644 index 000000000..dcd976f96 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs @@ -0,0 +1,18 @@ +using Artemis.Core; +using RGB.NET.Core; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Device.Tabs +{ + public class DeviceInfoTabViewModel : Screen + { + public DeviceInfoTabViewModel(ArtemisDevice device) + { + Device = device; + DisplayName = "INFO"; + } + + public bool IsKeyboard => Device.RgbDevice is IKeyboard; + public ArtemisDevice Device { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml similarity index 53% rename from src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml rename to src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml index 6c67d140b..37831d39b 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml @@ -1,31 +1,34 @@ - + d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}"> + + + - + d:DataContext="{d:DesignInstance Type={x:Type core:ArtemisLed}}" + CanUserSortColumns="True" + IsReadOnly="True" + CanUserAddRows="False" + AutoGenerateColumns="False" + materialDesign:DataGridAssist.CellPadding="13 8 8 8" + materialDesign:DataGridAssist.ColumnHeaderPadding="8" + SelectedItem="{Binding Parent.SelectedLed}" + CanUserResizeRows="False" + Margin="10"> - + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs similarity index 83% rename from src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs rename to src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs index c2339bd93..d57f8cb1a 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Device/Tabs/DeviceLedsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs @@ -1,7 +1,7 @@ using Artemis.Core; using Stylet; -namespace Artemis.UI.Screens.Settings.Debug.Device.Tabs +namespace Artemis.UI.Screens.Settings.Device.Tabs { public class DeviceLedsTabViewModel : Screen { diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml new file mode 100644 index 000000000..56da0c5bd --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + Surface properties + + + + + + + + + + + + + Color calibration + + + + Use the sliders below to adjust the colors of your device so that it matches your other devices. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Custom layout + + + Select a custom layout below if you want to change the appearance and/or LEDs of this device. + + + + + + + Layout path + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs similarity index 85% rename from src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs rename to src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs index ad06f0ca0..42fffa621 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -5,68 +5,46 @@ using System.Windows.Input; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services; -using MaterialDesignThemes.Wpf; using Ookii.Dialogs.Wpf; using SkiaSharp; using Stylet; -namespace Artemis.UI.Screens.SurfaceEditor.Dialogs +namespace Artemis.UI.Screens.Settings.Device.Tabs { - public class SurfaceDeviceConfigViewModel : DialogViewModelBase + public class DevicePropertiesTabViewModel : Screen { private readonly ICoreService _coreService; - private readonly IRgbService _rgbService; private readonly IMessageService _messageService; - private readonly double _initialRedScale; - private readonly double _initialGreenScale; - private readonly double _initialBlueScale; + private readonly IRgbService _rgbService; + private double _blueScale; + private SKColor _currentColor; + private bool _displayOnDevices; + private double _greenScale; + private double _initialBlueScale; + private double _initialGreenScale; + private double _initialRedScale; + private double _redScale; private int _rotation; private double _scale; private int _x; private int _y; - private double _redScale; - private double _greenScale; - private double _blueScale; - private SKColor _currentColor; - private bool _displayOnDevices; - public SurfaceDeviceConfigViewModel(ArtemisDevice device, + public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IMessageService messageService, - IModelValidator validator) : base(validator) + IModelValidator validator) : base(validator) { _coreService = coreService; _rgbService = rgbService; _messageService = messageService; Device = device; - - X = (int) Device.X; - Y = (int) Device.Y; - Scale = Device.Scale; - Rotation = (int) Device.Rotation; - RedScale = Device.RedScale * 100d; - GreenScale = Device.GreenScale * 100d; - BlueScale = Device.BlueScale * 100d; - //we need to store the initial values to be able to restore them when the user clicks "Cancel" - _initialRedScale = Device.RedScale; - _initialGreenScale = Device.GreenScale; - _initialBlueScale = Device.BlueScale; - CurrentColor = SKColors.White; - _coreService.FrameRendering += OnFrameRendering; - Device.PropertyChanged += DeviceOnPropertyChanged; + DisplayName = "PROPERTIES"; } public ArtemisDevice Device { get; } - public override void OnDialogClosed(object sender, DialogClosingEventArgs e) - { - _coreService.FrameRendering -= OnFrameRendering; - Device.PropertyChanged -= DeviceOnPropertyChanged; - base.OnDialogClosed(sender, e); - } - public int X { get => _x; @@ -121,27 +99,6 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs set => SetAndNotify(ref _displayOnDevices, value); } - public async Task Accept() - { - await ValidateAsync(); - if (HasErrors) - return; - - _coreService.ModuleRenderingDisabled = true; - await Task.Delay(100); - - Device.X = X; - Device.Y = Y; - Device.Scale = Scale; - Device.Rotation = Rotation; - Device.RedScale = RedScale / 100d; - Device.GreenScale = GreenScale / 100d; - Device.BlueScale = BlueScale / 100d; - - _coreService.ModuleRenderingDisabled = false; - Session.Close(true); - } - public void ApplyScaling() { Device.RedScale = RedScale / 100d; @@ -169,21 +126,58 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs } } - public override void Cancel() + public async Task Apply() + { + await ValidateAsync(); + if (HasErrors) + return; + + _coreService.ModuleRenderingDisabled = true; + await Task.Delay(100); + + Device.X = X; + Device.Y = Y; + Device.Scale = Scale; + Device.Rotation = Rotation; + Device.RedScale = RedScale / 100d; + Device.GreenScale = GreenScale / 100d; + Device.BlueScale = BlueScale / 100d; + + _coreService.ModuleRenderingDisabled = false; + } + + public void Reset() { Device.RedScale = _initialRedScale; Device.GreenScale = _initialGreenScale; Device.BlueScale = _initialBlueScale; - - base.Cancel(); } + protected override void OnActivate() + { + X = (int) Device.X; + Y = (int) Device.Y; + Scale = Device.Scale; + Rotation = (int) Device.Rotation; + RedScale = Device.RedScale * 100d; + GreenScale = Device.GreenScale * 100d; + BlueScale = Device.BlueScale * 100d; + //we need to store the initial values to be able to restore them when the user clicks "Cancel" + _initialRedScale = Device.RedScale; + _initialGreenScale = Device.GreenScale; + _initialBlueScale = Device.BlueScale; + CurrentColor = SKColors.White; + _coreService.FrameRendering += OnFrameRendering; + Device.PropertyChanged += DeviceOnPropertyChanged; + + base.OnActivate(); + } + + #region Event handlers + private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Device.CustomLayoutPath)) - { - _rgbService.ApplyBestDeviceLayout(Device); - } + if (e.PropertyName == nameof(Device.CustomLayoutPath)) _rgbService.ApplyBestDeviceLayout(Device); } private void OnFrameRendering(object sender, FrameRenderingEventArgs e) @@ -197,5 +191,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs }; e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint); } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/SurfaceDeviceConfigViewModelValidator.cs similarity index 73% rename from src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs rename to src/Artemis.UI/Screens/Settings/Device/Tabs/SurfaceDeviceConfigViewModelValidator.cs index 764bb36db..78de61ba7 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/SurfaceDeviceConfigViewModelValidator.cs @@ -1,10 +1,10 @@ using FluentValidation; -namespace Artemis.UI.Screens.SurfaceEditor.Dialogs +namespace Artemis.UI.Screens.Settings.Device.Tabs { - public class SurfaceDeviceConfigViewModelValidator : AbstractValidator + public class DevicePropertiesTabViewModelValidator : AbstractValidator { - public SurfaceDeviceConfigViewModelValidator() + public DevicePropertiesTabViewModelValidator() { RuleFor(m => m.X).GreaterThanOrEqualTo(0).WithMessage("X-coordinate must be 0 or greater"); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml index ced4c9131..a6564ec50 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml @@ -54,12 +54,6 @@ - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index b2412a473..1700dc144 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -10,6 +10,7 @@ using System.Windows.Navigation; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Extensions; +using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Shared; using Artemis.UI.Screens.SurfaceEditor.Dialogs; using Artemis.UI.Screens.SurfaceEditor.Visualization; @@ -25,8 +26,9 @@ namespace Artemis.UI.Screens.SurfaceEditor { private readonly ICoreService _coreService; private readonly IDeviceService _deviceService; + private readonly IWindowManager _windowManager; + private readonly IDeviceDebugVmFactory _deviceDebugVmFactory; private readonly IDialogService _dialogService; - private readonly IInputService _inputService; private readonly IRgbService _rgbService; private readonly ISettingsService _settingsService; private Cursor _cursor; @@ -39,7 +41,8 @@ namespace Artemis.UI.Screens.SurfaceEditor IDialogService dialogService, ISettingsService settingsService, IDeviceService deviceService, - IInputService inputService) + IWindowManager windowManager, + IDeviceDebugVmFactory deviceDebugVmFactory) { DisplayName = "Surface Editor"; SelectionRectangle = new RectangleGeometry(); @@ -55,7 +58,8 @@ namespace Artemis.UI.Screens.SurfaceEditor _dialogService = dialogService; _settingsService = settingsService; _deviceService = deviceService; - _inputService = inputService; + _windowManager = windowManager; + _deviceDebugVmFactory = deviceDebugVmFactory; } public BindableCollection SurfaceDeviceViewModels { get; } @@ -235,22 +239,14 @@ namespace Artemis.UI.Screens.SurfaceEditor _rgbService.SaveDevices(); } - public async Task ViewProperties(ArtemisDevice device) + public void ViewProperties(ArtemisDevice device) { - object madeChanges = await _dialogService.ShowDialog( - new Dictionary {{"device", device}} - ); - - if ((bool) madeChanges) - _rgbService.SaveDevice(device); + _windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(device)); } public async Task DetectInput(ArtemisDevice device) { - object madeChanges = await _dialogService.ShowDialog( - new Dictionary {{"device", device}} - ); - + object madeChanges = await _dialogService.ShowDialog(new Dictionary {{"device", device}}); if ((bool) madeChanges) _rgbService.SaveDevice(device); } From 72f8175dc112d9c6c281388ebdd3f0a14953d8c2 Mon Sep 17 00:00:00 2001 From: Robert Beekman Date: Tue, 2 Mar 2021 19:26:27 +0100 Subject: [PATCH 12/21] Update README.md [skip ci] --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 90d3634c5..064e062dd 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ Artemis 1 is no longer supported and Artemis 2 is in active development. This en **Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!) **Plugin documentation**: https://artemis-rgb.com/docs/ -**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested. If you run into any issues please let us know on Discord.** +**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested. If you run into any issues please let us know on Discord.** +A full list of supported devices can be found on the wiki [here](https://wiki.artemis-rgb.com/en/guides/user/devices). #### Want to build? Follow these instructions 1. Create a central folder like ```C:\Repos``` From b5f31553c6d32d087a04d87776e09ac1eca07d24 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 2 Mar 2021 20:02:33 +0100 Subject: [PATCH 13/21] Auto-update - Move installer to application data --- src/Artemis.UI/Services/UpdateService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Artemis.UI/Services/UpdateService.cs b/src/Artemis.UI/Services/UpdateService.cs index ac00d5426..8ad007fdd 100644 --- a/src/Artemis.UI/Services/UpdateService.cs +++ b/src/Artemis.UI/Services/UpdateService.cs @@ -53,7 +53,7 @@ namespace Artemis.UI.Services private async Task UpdateInstaller() { string downloadUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe"; - string installerDirectory = Path.Combine(Constants.ApplicationFolder, "Installer"); + string installerDirectory = Path.Combine(Constants.DataFolder, "installer"); string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe"); _logger.Information("UpdateInstaller: Downloading installer from {downloadUrl}", downloadUrl); @@ -132,7 +132,7 @@ namespace Artemis.UI.Services // Ensure the installer is up-to-date, get installer build info DevOpsBuild buildInfo = await GetBuildInfo(6); - string installerPath = Path.Combine(Constants.ApplicationFolder, "Installer", "Artemis.Installer.exe"); + string installerPath = Path.Combine(Constants.DataFolder, "installer", "Artemis.Installer.exe"); // Always update installer if it is missing ^^ if (!File.Exists(installerPath)) From 267e8e6a1ec1471cebc7ce12be4427d01ed55add Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 2 Mar 2021 20:22:49 +0100 Subject: [PATCH 14/21] Data bindings - Rework internals to support more situations --- .../Converters/FloatDataBindingConverter.cs | 3 - .../Properties/BoolLayerProperty.cs | 2 +- .../Properties/ColorGradientLayerProperty.cs | 54 ++++++++- .../Properties/FloatLayerProperty.cs | 2 +- .../Properties/FloatRangeLayerProperty.cs | 6 +- .../Properties/IntLayerProperty.cs | 2 +- .../Properties/IntRangeLayerProperty.cs | 4 +- .../Properties/SKColorLayerProperty.cs | 2 +- .../Properties/SKPointLayerProperty.cs | 5 +- .../Properties/SKSizeLayerProperty.cs | 4 +- .../Profile/DataBindings/DataBinding.cs | 4 +- .../DataBindings/DataBindingConverter.cs | 108 +----------------- .../DataBindings/DataBindingRegistration.cs | 32 +++--- .../Profile/LayerProperties/LayerProperty.cs | 27 +++-- .../DataBindings/DataBindingViewModel.cs | 6 +- .../Visualization/ProfileViewModel.cs | 2 +- 16 files changed, 109 insertions(+), 154 deletions(-) diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs index b076134bb..75818ac95 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs @@ -36,9 +36,6 @@ namespace Artemis.Core /// public override void ApplyValue(float value) { - if (ValueTypeSetExpression == null) - return; - if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max) value = Math.Min(value, max); if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min) diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index bfeb477f4..567ef6002 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -6,7 +6,7 @@ internal BoolLayerProperty() { KeyframesSupported = false; - RegisterDataBindingProperty(b => b, new GeneralDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new GeneralDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 449461bec..39f02c655 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,4 +1,6 @@ -namespace Artemis.Core +using SkiaSharp; + +namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty @@ -6,12 +8,33 @@ internal ColorGradientLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; + DataBindingsSupported = true; DefaultValue = new ColorGradient(); - + CurrentValueSet += OnCurrentValueSet; } + private void CreateDataBindingRegistrations() + { + ClearDataBindingProperties(); + if (CurrentValue == null) + return; + + for (int index = 0; index < CurrentValue.Stops.Count; index++) + { + int stopIndex = index; + DataBindingRegistration registerDataBindingProperty = RegisterDataBindingProperty( + gradient => gradient.Stops[stopIndex].Color, + (gradient, value) => gradient.Stops[stopIndex].Color = value, + new ColorStopDataBindingConverter(), + $"Color #{stopIndex + 1}" + ); + + registerDataBindingProperty.DisplayName = $"Color #{stopIndex + 1}"; + } + } + + /// /// Implicitly converts an to a /// @@ -31,6 +54,8 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); + + CreateDataBindingRegistrations(); } #region Overrides of LayerProperty @@ -41,10 +66,31 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); - + base.OnInitialize(); } #endregion } + + internal class ColorStopDataBindingConverter : DataBindingConverter + { + public ColorStopDataBindingConverter() + { + SupportsInterpolate = true; + SupportsSum = true; + } + + /// + public override SKColor Sum(SKColor a, SKColor b) + { + return a.Sum(b); + } + + /// + public override SKColor Interpolate(SKColor a, SKColor b, double progress) + { + return a.Interpolate(b, (float) progress); + } + } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index 55221deaf..589244657 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -5,7 +5,7 @@ { internal FloatLayerProperty() { - RegisterDataBindingProperty(value => value, new FloatDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new FloatDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 4b54aca95..74baa44f9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,9 +5,9 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter()); - + RegisterDataBindingProperty(value => value.Start, (value, newValue) => value.Start = newValue, new FloatDataBindingConverter(), "Start"); + RegisterDataBindingProperty(value => value.End, (value, newValue) => value.End = newValue, new FloatDataBindingConverter(), "End"); + CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index 7edd5241d..8d1fff755 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal IntLayerProperty() { - RegisterDataBindingProperty(value => value, new IntDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new IntDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 2a9f498fe..8ca36f876 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -5,8 +5,8 @@ { internal IntRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new IntDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new IntDataBindingConverter()); + RegisterDataBindingProperty(value => value.Start, (value, newValue) => value.Start = newValue, new IntDataBindingConverter(), "Start"); + RegisterDataBindingProperty(value => value.End, (value, newValue) => value.End = newValue, new IntDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index 366c4aa49..e08b9aaf6 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal SKColorLayerProperty() { - RegisterDataBindingProperty(value => value, new SKColorDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new SKColorDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 74befb209..f32a312c0 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,8 +7,9 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter()); - RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter()); + RegisterDataBindingProperty(value => value.X, (value, newValue) => value.X = newValue, new FloatDataBindingConverter(), "X"); + RegisterDataBindingProperty(value => value.Y, (value, newValue) => value.Y = newValue, new FloatDataBindingConverter(), "Y"); + } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index 0d98286b6..d018b4fb5 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter()); - RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter()); + RegisterDataBindingProperty(value => value.Height, (value, newValue) => value.Height = newValue, new FloatDataBindingConverter(), "Height"); + RegisterDataBindingProperty(value => value.Width, (value, newValue) => value.Width = newValue, new FloatDataBindingConverter(), "Width"); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 18c18894c..44370e498 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -97,7 +97,7 @@ namespace Artemis.Core /// public Type? GetTargetType() { - return Registration?.PropertyExpression.ReturnType; + return Registration?.Getter.Method.ReturnType; } private void ResetEasing(TProperty value) @@ -293,7 +293,7 @@ namespace Artemis.Core // Don't save an invalid state if (Registration != null) - Entity.TargetExpression = Registration.PropertyExpression.ToString(); + Entity.TargetExpression = Registration.Getter.ToString(); Entity.EasingTime = EasingTime; Entity.EasingFunction = (int) EasingFunction; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index fe4254c22..a7ec330ed 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -1,6 +1,4 @@ using System; -using System.Linq.Expressions; -using System.Reflection; namespace Artemis.Core { @@ -10,21 +8,6 @@ namespace Artemis.Core /// public abstract class DataBindingConverter : IDataBindingConverter { - /// - /// Gets a dynamically compiled getter pointing to the data bound property - /// - public Func? GetExpression { get; private set; } - - /// - /// Gets a dynamically compiled setter pointing to the data bound property used for value types - /// - public Action? ValueTypeSetExpression { get; private set; } - - /// - /// Gets a dynamically compiled setter pointing to the data bound property used for reference types - /// - public Action? ReferenceTypeSetExpression { get; private set; } - /// /// Gets the data binding this converter is applied to /// @@ -40,9 +23,6 @@ namespace Artemis.Core /// public bool SupportsInterpolate { get; protected set; } - /// - public Type SupportedType => typeof(TProperty); - /// /// Returns the sum of and /// @@ -65,12 +45,9 @@ namespace Artemis.Core /// public virtual void ApplyValue(TProperty value) { - if (DataBinding == null) + if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - if (ReferenceTypeSetExpression != null) - ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value); - else if (ValueTypeSetExpression != null) - ValueTypeSetExpression(value); + DataBinding.Registration.Setter(DataBinding.LayerProperty.CurrentValue, value); } /// @@ -78,9 +55,9 @@ namespace Artemis.Core /// public virtual TProperty GetValue() { - if (DataBinding == null || GetExpression == null) + if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - return GetExpression(DataBinding.LayerProperty.CurrentValue); + return DataBinding.Registration.Getter(DataBinding.LayerProperty.CurrentValue); } /// @@ -104,83 +81,10 @@ namespace Artemis.Core throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration"); DataBinding = dataBinding; - GetExpression = dataBinding.Registration.PropertyExpression.Compile(); - CreateSetExpression(); - OnInitialized(); } - private void CreateSetExpression() - { - // If the registration does not point towards a member of LayerProperty.CurrentValue, assign directly to LayerProperty.CurrentValue - if (DataBinding!.Registration?.Member == null) - { - CreateSetCurrentValueExpression(); - return; - } - - // Ensure the member of LayerProperty.CurrentValue has a setter - MethodInfo? setterMethod = null; - if (DataBinding.Registration.Member is PropertyInfo propertyInfo) - setterMethod = propertyInfo.GetSetMethod(); - // If there is no setter, the built-in data binding cannot do its job, stay null - if (setterMethod == null) - return; - - // If LayerProperty.CurrentValue is a value type, assign it directly to LayerProperty.CurrentValue after applying the changes - if (typeof(TLayerProperty).IsValueType) - CreateSetValueTypeExpression(); - // If it is a reference type it can safely be updated by its reference - else - CreateSetReferenceTypeExpression(); - } - - private void CreateSetReferenceTypeExpression() - { - if (DataBinding!.Registration?.Member == null) - throw new ArtemisCoreException("Cannot create value setter for data binding without a registration"); - - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ParameterExpression parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue"); - MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member); - BinaryExpression assignment = Expression.Assign(memberAccess, propertyValue); - Expression> referenceTypeLambda = Expression.Lambda>(assignment, parameter, propertyValue); - - ReferenceTypeSetExpression = referenceTypeLambda.Compile(); - } - - private void CreateSetValueTypeExpression() - { - if (DataBinding!.Registration?.Member == null) - throw new ArtemisCoreException("Cannot create value setter for data binding without a registration"); - - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ParameterExpression variableCurrent = Expression.Variable(typeof(TLayerProperty), "current"); - ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty); - MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, - DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); - - BlockExpression body = Expression.Block( - new[] {variableCurrent}, - Expression.Assign(variableCurrent, layerPropertyMemberAccess), - Expression.Assign(Expression.MakeMemberAccess(variableCurrent, DataBinding.Registration.Member), propertyValue), - Expression.Assign(layerPropertyMemberAccess, variableCurrent) - ); - - Expression> valueTypeLambda = Expression.Lambda>(body, propertyValue); - ValueTypeSetExpression = valueTypeLambda.Compile(); - } - - private void CreateSetCurrentValueExpression() - { - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ConstantExpression layerProperty = Expression.Constant(DataBinding!.LayerProperty); - MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, - DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); - - BinaryExpression body = Expression.Assign(layerPropertyMemberAccess, propertyValue); - Expression> lambda = Expression.Lambda>(body, propertyValue); - ValueTypeSetExpression = lambda.Compile(); - } + /// + public Type SupportedType => typeof(TProperty); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index 00346e68b..e43c28550 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -1,7 +1,5 @@ using System; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -9,16 +7,16 @@ namespace Artemis.Core /// public class DataBindingRegistration : IDataBindingRegistration { - internal DataBindingRegistration(LayerProperty layerProperty, - DataBindingConverter converter, - Expression> propertyExpression) + internal DataBindingRegistration(LayerProperty layerProperty, DataBindingConverter converter, + Func getter, + Action setter, + string displayName) { LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression)); - - if (propertyExpression.Body is MemberExpression memberExpression) - Member = memberExpression.Member; + Getter = getter ?? throw new ArgumentNullException(nameof(getter)); + Setter = setter ?? throw new ArgumentNullException(nameof(setter)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); } /// @@ -32,15 +30,19 @@ namespace Artemis.Core public DataBindingConverter Converter { get; } /// - /// Gets the expression that that accesses the property + /// Gets the function to call to get the value of the property /// - public Expression> PropertyExpression { get; } + public Func Getter { get; } /// - /// Gets the member the targets - /// if the is not a member expression + /// Gets the action to call to set the value of the property /// - public MemberInfo? Member { get; } + public Action Setter { get; } + + /// + /// Gets or sets the display name of the data binding registration + /// + public string DisplayName { get; set; } /// /// Gets the data binding created using this registration @@ -59,7 +61,7 @@ namespace Artemis.Core if (DataBinding != null) return DataBinding; - DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString()); + DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == Getter.ToString()); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index bb9760a91..b184f6e24 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -393,7 +393,7 @@ namespace Artemis.Core throw new ObjectDisposedException("LayerProperty"); IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.PropertyExpression.ToString() == expression); + registration.Getter.ToString() == expression); return (DataBindingRegistration?) match; } @@ -410,21 +410,30 @@ namespace Artemis.Core /// Registers a data binding property so that is available to the data binding system /// /// The type of the layer property - /// The expression pointing to the value to register + /// The function to call to get the value of the property + /// The action to call to set the value of the property /// The converter to use while applying the data binding - public void RegisterDataBindingProperty(Expression> propertyExpression, DataBindingConverter converter) + /// The display name of the data binding property + public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, + string displayName) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); - if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter) - throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'"); + DataBindingRegistration registration = new(this, converter, getter, setter, displayName); + _dataBindingRegistrations.Add(registration); + return registration; + } - if (converter.SupportedType != propertyExpression.ReturnType) - throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " + - "because the provided converter does not support the property's type"); + /// + /// Removes all data binding properties so they are no longer available to the data binding system + /// + public void ClearDataBindingProperties() + { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); - _dataBindingRegistrations.Add(new DataBindingRegistration(this, converter, propertyExpression)); + _dataBindingRegistrations.Clear(); } /// diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 0ba67c4ab..105af6c65 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -38,11 +38,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _profileEditorService = profileEditorService; _dataBindingsVmFactory = dataBindingsVmFactory; - if (Registration.Member != null) - DisplayName = Registration.Member.Name.ToUpper(); - else - DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper(); - + DisplayName = Registration.DisplayName.ToUpper(); AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); EasingViewModels = new BindableCollection(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 071884dcd..f3620ba15 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -302,7 +302,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization TimeSpan delta = DateTime.Now - _lastUpdate; _lastUpdate = DateTime.Now; - if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null || _profileEditorService.Playing) + if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null) return; foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() From f4ad41cbd45f9b3e73fe8b5ef0762fdf23245daf Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 2 Mar 2021 21:41:56 +0100 Subject: [PATCH 15/21] Data bindings - Simplified getter/setter code --- .../Properties/BoolLayerProperty.cs | 2 +- .../Properties/ColorGradientLayerProperty.cs | 8 +++----- .../Properties/FloatLayerProperty.cs | 2 +- .../Properties/FloatRangeLayerProperty.cs | 6 +++--- .../DefaultTypes/Properties/IntLayerProperty.cs | 2 +- .../Properties/IntRangeLayerProperty.cs | 4 ++-- .../Properties/SKColorLayerProperty.cs | 2 +- .../Properties/SKPointLayerProperty.cs | 5 ++--- .../Properties/SKSizeLayerProperty.cs | 4 ++-- .../Models/Profile/DataBindings/DataBinding.cs | 4 ++-- .../DataBindings/DataBindingConverter.cs | 4 ++-- .../DataBindings/DataBindingRegistration.cs | 16 ++++++---------- .../DataBindings/IDataBindingRegistration.cs | 5 +++++ .../Profile/LayerProperties/LayerProperty.cs | 17 +++++++---------- .../Profile/DataBindings/DataBindingEntity.cs | 2 +- .../PropertyInput/BoolPropertyInputView.xaml | 2 +- .../PropertyInput/BoolPropertyInputViewModel.cs | 16 +++++++++++++--- .../PropertyInput/BrushPropertyInputView.xaml | 4 ++-- .../BrushPropertyInputViewModel.cs | 2 +- .../ColorGradientPropertyInputView.xaml | 3 +-- .../ColorGradientPropertyInputViewModel.cs | 2 +- .../PropertyInput/EnumPropertyInputView.xaml | 2 +- .../PropertyInput/EnumPropertyInputViewModel.cs | 2 +- .../PropertyInput/FloatPropertyInputView.xaml | 3 +-- .../FloatPropertyInputViewModel.cs | 4 ++-- .../FloatRangePropertyInputView.xaml | 3 +-- .../FloatRangePropertyInputViewModel.cs | 6 +++--- .../PropertyInput/IntPropertyInputView.xaml | 3 +-- .../PropertyInput/IntPropertyInputViewModel.cs | 4 ++-- .../IntRangePropertyInputView.xaml | 3 +-- .../IntRangePropertyInputViewModel.cs | 6 +++--- .../PropertyInput/SKColorPropertyInputView.xaml | 3 +-- .../SKColorPropertyInputViewModel.cs | 4 ++-- .../PropertyInput/SKPointPropertyInputView.xaml | 3 +-- .../SKPointPropertyInputViewModel.cs | 6 +++--- .../PropertyInput/SKSizePropertyInputView.xaml | 3 +-- .../SKSizePropertyInputViewModel.cs | 6 +++--- src/Artemis.UI/Services/RegistrationService.cs | 2 +- 38 files changed, 86 insertions(+), 89 deletions(-) diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index 567ef6002..99a7b614a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -6,7 +6,7 @@ internal BoolLayerProperty() { KeyframesSupported = false; - RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new GeneralDataBindingConverter(), "Value"); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 39f02c655..931e4d03d 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -23,14 +23,12 @@ namespace Artemis.Core for (int index = 0; index < CurrentValue.Stops.Count; index++) { int stopIndex = index; - DataBindingRegistration registerDataBindingProperty = RegisterDataBindingProperty( - gradient => gradient.Stops[stopIndex].Color, - (gradient, value) => gradient.Stops[stopIndex].Color = value, + RegisterDataBindingProperty( + () => CurrentValue.Stops[stopIndex].Color, + value => CurrentValue.Stops[stopIndex].Color = value, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}" ); - - registerDataBindingProperty.DisplayName = $"Color #{stopIndex + 1}"; } } diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index 589244657..cb1a6eae8 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -5,7 +5,7 @@ { internal FloatLayerProperty() { - RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new FloatDataBindingConverter(), "Value"); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 74baa44f9..298e824f3 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,9 +5,9 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(value => value.Start, (value, newValue) => value.Start = newValue, new FloatDataBindingConverter(), "Start"); - RegisterDataBindingProperty(value => value.End, (value, newValue) => value.End = newValue, new FloatDataBindingConverter(), "End"); - + RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter(), "Start"); + RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter(), "End"); + CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index 8d1fff755..f69a1bdbc 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal IntLayerProperty() { - RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new IntDataBindingConverter(), "Value"); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 8ca36f876..474fa33e1 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -5,8 +5,8 @@ { internal IntRangeLayerProperty() { - RegisterDataBindingProperty(value => value.Start, (value, newValue) => value.Start = newValue, new IntDataBindingConverter(), "Start"); - RegisterDataBindingProperty(value => value.End, (value, newValue) => value.End = newValue, new IntDataBindingConverter(), "End"); + RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter(), "Start"); + RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index e08b9aaf6..46370568a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal SKColorLayerProperty() { - RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new SKColorDataBindingConverter(), "Value"); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index f32a312c0..3cd654ad9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,9 +7,8 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(value => value.X, (value, newValue) => value.X = newValue, new FloatDataBindingConverter(), "X"); - RegisterDataBindingProperty(value => value.Y, (value, newValue) => value.Y = newValue, new FloatDataBindingConverter(), "Y"); - + RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter(), "X"); + RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter(), "Y"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index d018b4fb5..3274c26f9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(value => value.Height, (value, newValue) => value.Height = newValue, new FloatDataBindingConverter(), "Height"); - RegisterDataBindingProperty(value => value.Width, (value, newValue) => value.Width = newValue, new FloatDataBindingConverter(), "Width"); + RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter(), "Width"); + RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter(), "Height"); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 44370e498..4bfbacc48 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -272,7 +272,7 @@ namespace Artemis.Core throw new ObjectDisposedException("DataBinding"); // General - DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.TargetExpression); + DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.Identifier); if (registration != null) ApplyRegistration(registration); @@ -293,7 +293,7 @@ namespace Artemis.Core // Don't save an invalid state if (Registration != null) - Entity.TargetExpression = Registration.Getter.ToString(); + Entity.Identifier = Registration.DisplayName; Entity.EasingTime = EasingTime; Entity.EasingFunction = (int) EasingFunction; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index a7ec330ed..2b3d7eb5a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -47,7 +47,7 @@ namespace Artemis.Core { if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - DataBinding.Registration.Setter(DataBinding.LayerProperty.CurrentValue, value); + DataBinding.Registration.Setter(value); } /// @@ -57,7 +57,7 @@ namespace Artemis.Core { if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - return DataBinding.Registration.Getter(DataBinding.LayerProperty.CurrentValue); + return DataBinding.Registration.Getter(); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index e43c28550..78e233fe1 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -8,9 +8,7 @@ namespace Artemis.Core public class DataBindingRegistration : IDataBindingRegistration { internal DataBindingRegistration(LayerProperty layerProperty, DataBindingConverter converter, - Func getter, - Action setter, - string displayName) + Func getter, Action setter, string displayName) { LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); Converter = converter ?? throw new ArgumentNullException(nameof(converter)); @@ -32,17 +30,15 @@ namespace Artemis.Core /// /// Gets the function to call to get the value of the property /// - public Func Getter { get; } + public Func Getter { get; } /// /// Gets the action to call to set the value of the property /// - public Action Setter { get; } + public Action Setter { get; } - /// - /// Gets or sets the display name of the data binding registration - /// - public string DisplayName { get; set; } + /// + public string DisplayName { get; } /// /// Gets the data binding created using this registration @@ -61,7 +57,7 @@ namespace Artemis.Core if (DataBinding != null) return DataBinding; - DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == Getter.ToString()); + DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs index fb6220951..8e1de1f0a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs @@ -5,6 +5,11 @@ /// public interface IDataBindingRegistration { + /// + /// Gets or sets the display name of the data binding registration + /// + string DisplayName { get; } + /// /// Returns the data binding applied using this registration /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index b184f6e24..fe93cbcb3 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -379,21 +379,16 @@ namespace Artemis.Core public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null); /// - /// Gets a data binding registration by the expression used to register it + /// Gets a data binding registration by the display name used to register it /// Note: The expression must exactly match the one used to register the data binding /// - public DataBindingRegistration? GetDataBindingRegistration(Expression> propertyExpression) - { - return GetDataBindingRegistration(propertyExpression.ToString()); - } - - internal DataBindingRegistration? GetDataBindingRegistration(string expression) + public DataBindingRegistration? GetDataBindingRegistration(string identifier) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.Getter.ToString() == expression); + registration.DisplayName == identifier); return (DataBindingRegistration?) match; } @@ -414,13 +409,15 @@ namespace Artemis.Core /// The action to call to set the value of the property /// The converter to use while applying the data binding /// The display name of the data binding property - public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, + public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, string displayName) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); + if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName)) + throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); - DataBindingRegistration registration = new(this, converter, getter, setter, displayName); + DataBindingRegistration registration = new(this, converter, getter, setter, displayName); _dataBindingRegistrations.Add(registration); return registration; } diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs index 0a0dc030c..6dd27b85a 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings { public class DataBindingEntity { - public string TargetExpression { get; set; } + public string Identifier { get; set; } public TimeSpan EasingTime { get; set; } public int EasingFunction { get; set; } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml index 16f480241..ebf357f9e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml @@ -1,4 +1,4 @@ - { + private List _registrations; + public BoolPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { + _registrations = layerProperty.GetAllDataBindingRegistrations(); } - public bool IsEnabled => true; + public bool IsEnabled => _registrations.Any(r => r.GetDataBinding() != null); + + protected override void OnDataBindingsChanged() + { + NotifyOfPropertyChange(nameof(IsEnabled)); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml index 15c14f988..aa4e288d3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml @@ -1,12 +1,12 @@ - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs index d0b5e1b16..20409eb1c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -7,7 +7,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.PropertyInput +namespace Artemis.UI.DefaultTypes.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml index 13ea3a9c9..ef17ba34e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml @@ -1,9 +1,8 @@ - { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml index 732d1648f..818914562 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml @@ -1,4 +1,4 @@ - : PropertyInputViewModel where T : Enum { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml index 3c9bd2105..65e27826f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml @@ -1,11 +1,10 @@ - { @@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput public FloatPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml index e88828009..aabe66a33 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); - _endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); + _startRegistration = layerProperty.GetDataBindingRegistration("Start"); + _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public float Start diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml index 14fab93f1..4cae90771 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml @@ -1,11 +1,10 @@ - { @@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput public IntPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml index 62e4b8570..40a0e815a 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); - _endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); + _startRegistration = layerProperty.GetDataBindingRegistration("Start"); + _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public int Start diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml index fdc5a6c1b..6861259bb 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml @@ -1,10 +1,9 @@ - { @@ -11,7 +11,7 @@ namespace Artemis.UI.PropertyInput public SKColorPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml index 34e679a0c..1f15efc15 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml @@ -1,11 +1,10 @@ - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs index 8d7448f8b..ac32cbaf3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs @@ -6,7 +6,7 @@ using FluentValidation; using SkiaSharp; using Stylet; -namespace Artemis.UI.PropertyInput +namespace Artemis.UI.DefaultTypes.PropertyInput { public class SKPointPropertyInputViewModel : PropertyInputViewModel { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput public SKPointPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _xRegistration = layerProperty.GetDataBindingRegistration(point => point.X); - _yRegistration = layerProperty.GetDataBindingRegistration(point => point.Y); + _xRegistration = layerProperty.GetDataBindingRegistration("X"); + _yRegistration = layerProperty.GetDataBindingRegistration("Y"); } public float X diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml index 9cca2e62b..0f5dfc93c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -18,8 +18,8 @@ namespace Artemis.UI.PropertyInput public SKSizePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _widthRegistration = layerProperty.GetDataBindingRegistration(size => size.Width); - _heightRegistration = layerProperty.GetDataBindingRegistration(size => size.Height); + _widthRegistration = layerProperty.GetDataBindingRegistration("Width"); + _heightRegistration = layerProperty.GetDataBindingRegistration("Height"); } // Since SKSize is immutable we need to create properties that replace the SKSize entirely diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index ffbdc2958..0d51ce376 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -4,9 +4,9 @@ using Artemis.Core.Services; using Artemis.UI.Controllers; using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Input; +using Artemis.UI.DefaultTypes.PropertyInput; using Artemis.UI.InputProviders; using Artemis.UI.Ninject; -using Artemis.UI.PropertyInput; using Artemis.UI.Shared.Services; using Serilog; From 18592502fd89a725700e6d7ceb011f2a08084b20 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 3 Mar 2021 20:19:34 +0100 Subject: [PATCH 16/21] Profiles - Fixed event rapid trigger mode not triggering a save Events - Fixed replacing the event during update breaking existing event conditions --- .../Conditions/DataModelConditionEvent.cs | 36 ++++++------------- .../DisplayConditionsView.xaml | 6 ++-- .../DisplayConditionsViewModel.cs | 24 ++++++++++++- .../Debug/Tabs/DataModelDebugViewModel.cs | 2 +- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs index 49a2a0072..8ae6d5411 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs @@ -11,9 +11,8 @@ namespace Artemis.Core public class DataModelConditionEvent : DataModelConditionPart { private bool _disposed; - private IDataModelEvent? _event; - private bool _eventTriggered; private bool _reinitializing; + private DateTime _lastTrigger; /// /// Creates a new instance of the class @@ -53,22 +52,21 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataModelConditionEvent"); - // Ensure the event has not been replaced - if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent) - SubscribeToDataModelEvent(dataModelEvent); - - // Only evaluate to true once every time the event has been triggered - if (!_eventTriggered) + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + return false; + // Only evaluate to true once every time the event has been triggered since the last evaluation + if (dataModelEvent.LastTrigger <= _lastTrigger) return false; - _eventTriggered = false; + _lastTrigger = DateTime.Now; // If there is a child (root group), it must evaluate to true whenever the event triggered if (Children.Any()) - return Children[0].EvaluateObject(_event?.LastEventArgumentsUntyped); + return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped); // If there are no children, we always evaluate to true whenever the event triggered return true; + } /// @@ -132,7 +130,7 @@ namespace Artemis.Core // Target list EventPath?.Save(); Entity.EventPath = EventPath?.Entity; - + // Children Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); @@ -172,16 +170,9 @@ namespace Artemis.Core Entity.Children.Clear(); AddChild(new DataModelConditionGroup(this)); } - } - private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent) - { - if (_event != null) - _event.EventTriggered -= OnEventTriggered; - - _event = dataModelEvent; - if (_event != null) - _event.EventTriggered += OnEventTriggered; + if (EventPath?.GetValue() is IDataModelEvent dataModelEvent) + _lastTrigger = dataModelEvent.LastTrigger; } private Type? GetEventArgumentType() @@ -212,11 +203,6 @@ namespace Artemis.Core #region Event handlers - private void OnEventTriggered(object? sender, EventArgs e) - { - _eventTriggered = true; - } - private void EventPathOnPathValidated(object? sender, EventArgs e) { if (_reinitializing) diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index cc49f3d59..05c33df00 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -200,7 +200,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}"> RESTART @@ -215,7 +215,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IGNORE @@ -230,7 +230,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> COPY diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index a04d3d563..3f79b19e3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -38,7 +38,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public RenderProfileElement RenderProfileElement { get => _renderProfileElement; - set => SetAndNotify(ref _renderProfileElement, value); + set + { + if (!SetAndNotify(ref _renderProfileElement, value)) return; + NotifyOfPropertyChange(nameof(DisplayContinuously)); + NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); + NotifyOfPropertyChange(nameof(EventOverlapMode)); + } } public bool DisplayContinuously @@ -65,6 +71,17 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions } } + public TimeLineEventOverlapMode EventOverlapMode + { + get => RenderProfileElement?.Timeline.EventOverlapMode ?? TimeLineEventOverlapMode.Restart; + set + { + if (RenderProfileElement == null || RenderProfileElement?.Timeline.EventOverlapMode == value) return; + RenderProfileElement.Timeline.EventOverlapMode = value; + _profileEditorService.UpdateSelectedProfileElement(); + } + } + public bool ConditionBehaviourEnabled => RenderProfileElement != null; protected override void OnInitialActivate() @@ -119,5 +136,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); } + + public void EventTriggerModeSelected() + { + _profileEditorService.UpdateSelectedProfileElement(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index e2b83d7d0..0e0e97656 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -127,7 +127,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs lock (MainDataModel) { - MainDataModel.Update(_dataModelUIService, null); + MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true)); } } From c47e873510a56e0082bb256fc0fd8369149705d9 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Mar 2021 00:26:30 +0100 Subject: [PATCH 17/21] Plugin endpoints - Added DataModelJsonPluginEndPoint --- .../Artemis.Core.csproj.DotSettings | 1 + .../EndPoints/DataModelJsonPluginEndPoint.cs | 71 +++++++++++++++++++ .../EndpointExceptionEventArgs.cs | 0 .../EventArgs/EndpointRequestEventArgs.cs | 21 ++++++ .../WebServer/EndPoints/PluginEndPoint.cs | 28 ++++++++ .../WebServer/Interfaces/IWebServerService.cs | 20 ++++++ .../Services/WebServer/WebServerService.cs | 25 +++++++ 7 files changed, 166 insertions(+) create mode 100644 src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs rename src/Artemis.Core/Services/WebServer/EndPoints/{ => EventArgs}/EndpointExceptionEventArgs.cs (100%) create mode 100644 src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 7d6c52363..5a6e7bef3 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -68,6 +68,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs new file mode 100644 index 000000000..30532aa2b --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; +using EmbedIO; +using Newtonsoft.Json; + +namespace Artemis.Core.Services +{ + /// + /// Represents a plugin web endpoint receiving an object of type and returning any + /// or . + /// Note: Both will be deserialized and serialized respectively using JSON. + /// + public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel + { + private readonly Module? _module; + private readonly DataModelExpansion? _dataModelExpansion; + + internal DataModelJsonPluginEndPoint(Module module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule) + { + _module = module ?? throw new ArgumentNullException(nameof(module)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + internal DataModelJsonPluginEndPoint(DataModelExpansion dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule) + { + _dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + /// + /// Whether or not the end point should throw an exception if deserializing the received JSON fails. + /// If set to malformed JSON is silently ignored; if set to malformed + /// JSON throws a . + /// + public bool ThrowOnFail { get; set; } + + #region Overrides of PluginEndPoint + + /// + protected override async Task ProcessRequest(IHttpContext context) + { + if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put) + throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls"); + + context.Response.ContentType = MimeType.Json; + + using TextReader reader = context.OpenRequestText(); + try + { + if (_module != null) + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel); + else + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel); + } + catch (JsonException) + { + if (ThrowOnFail) + throw; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/EndpointExceptionEventArgs.cs b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs similarity index 100% rename from src/Artemis.Core/Services/WebServer/EndPoints/EndpointExceptionEventArgs.cs rename to src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs new file mode 100644 index 000000000..6b483cc06 --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using EmbedIO; + +namespace Artemis.Core.Services +{ + /// + /// Provides data about endpoint request related events + /// + public class EndpointRequestEventArgs : EventArgs + { + internal EndpointRequestEventArgs(IHttpContext context) + { + Context = context; + } + + /// + /// Gets the HTTP context of the request + /// + public IHttpContext Context { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs index 504fd0eff..c62c41fe3 100644 --- a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs +++ b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs @@ -57,6 +57,16 @@ namespace Artemis.Core.Services /// public event EventHandler? RequestException; + /// + /// Occurs whenever a request is about to be processed + /// + public event EventHandler? ProcessingRequest; + + /// + /// Occurs whenever a request was processed + /// + public event EventHandler? ProcessedRequest; + /// /// Called whenever the end point has to process a request /// @@ -72,11 +82,29 @@ namespace Artemis.Core.Services RequestException?.Invoke(this, new EndpointExceptionEventArgs(e)); } + /// + /// Invokes the event + /// + protected virtual void OnProcessingRequest(IHttpContext context) + { + ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context)); + } + + /// + /// Invokes the event + /// + protected virtual void OnProcessedRequest(IHttpContext context) + { + ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context)); + } + internal async Task InternalProcessRequest(IHttpContext context) { try { + OnProcessingRequest(context); await ProcessRequest(context); + OnProcessedRequest(context); } catch (Exception e) { diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 704a4a19c..ef98d3fa0 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; @@ -43,6 +45,24 @@ namespace Artemis.Core.Services /// The resulting end point JsonPluginEndPoint AddResponsiveJsonEndPoint(PluginFeature feature, string endPointName, Func requestHandler); + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The module whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel; + + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The data model expansion whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel; + /// /// Adds a new endpoint for the given plugin feature receiving an a . /// diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index e19e6a4a9..8de5ce9e4 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; using Newtonsoft.Json; @@ -125,6 +127,29 @@ namespace Artemis.Core.Services return endPoint; } + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel + { + if (module == null) throw new ArgumentNullException(nameof(module)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(module, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel + { + if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(dataModelExpansion, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + + private void HandleDataModelRequest(Module module, T value) where T : DataModel + { + + } + public void RemovePluginEndPoint(PluginEndPoint endPoint) { PluginsModule.RemovePluginEndPoint(endPoint); From ef665115648015d018d061d9ec6f021bd13890b4 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Mar 2021 19:17:17 +0100 Subject: [PATCH 18/21] Data bindings - Update UI on registration changes Data bindings - Tweaked UI tabs to handle a large amount of properties better Color gradient property - Update data binding properties on stop add/remove --- .../Properties/ColorGradientLayerProperty.cs | 19 ++++++- .../Profile/LayerProperties/ILayerProperty.cs | 10 ++++ .../Profile/LayerProperties/LayerProperty.cs | 25 +++++++++ .../DataBindings/DataBindingViewModel.cs | 2 +- .../DataBindings/DataBindingsView.xaml | 9 +++- .../DataBindings/DataBindingsViewModel.cs | 54 +++++++++++++++++-- 6 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 931e4d03d..6eb4bdda2 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,10 +1,13 @@ -using SkiaSharp; +using System.ComponentModel; +using SkiaSharp; namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty { + private ColorGradient? _subscribedGradient; + internal ColorGradientLayerProperty() { KeyframesSupported = false; @@ -53,9 +56,23 @@ namespace Artemis.Core if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); + if (_subscribedGradient != BaseValue) + { + if (_subscribedGradient != null) + _subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged; + _subscribedGradient = BaseValue; + _subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged; + } + CreateDataBindingRegistrations(); } + private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count) + CreateDataBindingRegistrations(); + } + #region Overrides of LayerProperty /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index 33153f4ef..c42b1f74c 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -97,6 +97,16 @@ namespace Artemis.Core /// public event EventHandler? KeyframeRemoved; + /// + /// Occurs when a data binding property has been added + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + /// Occurs when all data binding properties have been removed + /// + public event EventHandler? DataBindingPropertiesCleared; + /// /// Occurs when a data binding has been enabled /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index fe93cbcb3..61f38c428 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -419,6 +419,8 @@ namespace Artemis.Core DataBindingRegistration registration = new(this, converter, getter, setter, displayName); _dataBindingRegistrations.Add(registration); + + OnDataBindingPropertyRegistered(); return registration; } @@ -431,6 +433,7 @@ namespace Artemis.Core throw new ObjectDisposedException("LayerProperty"); _dataBindingRegistrations.Clear(); + OnDataBindingPropertiesCleared(); } /// @@ -667,6 +670,12 @@ namespace Artemis.Core /// public event EventHandler? KeyframeRemoved; + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + public event EventHandler? DataBindingPropertiesCleared; + /// public event EventHandler? DataBindingEnabled; @@ -722,6 +731,22 @@ namespace Artemis.Core KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); } + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertyRegistered() + { + DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertiesCleared() + { + DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this)); + } + /// /// Invokes the event /// diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 105af6c65..b25710928 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -102,8 +102,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings protected override void OnInitialActivate() { - base.OnInitialActivate(); Initialize(); + base.OnInitialActivate(); } private void Initialize() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml index 35e1c740c..164f28796 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml @@ -6,11 +6,18 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index 23e73dcd1..00c72fc34 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -11,7 +14,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; + private ILayerProperty? _selectedDataBinding; private int _selectedItemIndex; + private bool _updating; public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) { @@ -24,9 +29,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings get => _selectedItemIndex; set => SetAndNotify(ref _selectedItemIndex, value); } - + private void CreateDataBindingViewModels() { + int oldIndex = SelectedItemIndex; Items.Clear(); ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding; @@ -37,26 +43,64 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving // and creating the actual data bindings - foreach (IDataBindingRegistration registration in registrations) - Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration)); + Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration))); - SelectedItemIndex = 0; + SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex; } private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) { CreateDataBindingViewModels(); + SubscribeToSelectedDataBinding(); + + SelectedItemIndex = 0; + } + + private void SubscribeToSelectedDataBinding() + { + if (_selectedDataBinding != null) + { + _selectedDataBinding.DataBindingPropertyRegistered -= DataBindingRegistrationsChanged; + _selectedDataBinding.DataBindingPropertiesCleared -= DataBindingRegistrationsChanged; + } + + _selectedDataBinding = _profileEditorService.SelectedDataBinding; + if (_selectedDataBinding != null) + { + _selectedDataBinding.DataBindingPropertyRegistered += DataBindingRegistrationsChanged; + _selectedDataBinding.DataBindingPropertiesCleared += DataBindingRegistrationsChanged; + } + } + + private void DataBindingRegistrationsChanged(object sender, LayerPropertyEventArgs e) + { + if (_updating) + return; + + _updating = true; + Execute.PostToUIThread(async () => + { + await Task.Delay(200); + CreateDataBindingViewModels(); + _updating = false; + }); } #region Overrides of Screen - /// protected override void OnInitialActivate() { _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; CreateDataBindingViewModels(); + SubscribeToSelectedDataBinding(); base.OnInitialActivate(); } + + protected override void OnActivate() + { + SelectedItemIndex = 0; + base.OnActivate(); + } protected override void OnClose() { From 6ba3e6941d8c12da33f6533ee79aa2f6c9e8b318 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Mar 2021 20:02:07 +0100 Subject: [PATCH 19/21] Color gradient property - Trigger PropertyChanged on data binding update --- .../Properties/ColorGradientLayerProperty.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 6eb4bdda2..387c2d9ef 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -26,12 +26,14 @@ namespace Artemis.Core for (int index = 0; index < CurrentValue.Stops.Count; index++) { int stopIndex = index; - RegisterDataBindingProperty( - () => CurrentValue.Stops[stopIndex].Color, - value => CurrentValue.Stops[stopIndex].Color = value, - new ColorStopDataBindingConverter(), - $"Color #{stopIndex + 1}" - ); + + void Setter(SKColor value) + { + CurrentValue.Stops[stopIndex].Color = value; + CurrentValue.OnColorValuesUpdated(); + } + + RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}"); } } @@ -58,12 +60,12 @@ namespace Artemis.Core if (_subscribedGradient != BaseValue) { - if (_subscribedGradient != null) + if (_subscribedGradient != null) _subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged; _subscribedGradient = BaseValue; _subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged; } - + CreateDataBindingRegistrations(); } From a57dda8485f1680ca663ad18f4581671febb1517 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Mar 2021 20:05:23 +0100 Subject: [PATCH 20/21] Data bindings - Added migration to identifier-based storage --- .../Migrations/M10BetterDataBindings.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/Artemis.Storage/Migrations/M10BetterDataBindings.cs diff --git a/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs b/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs new file mode 100644 index 000000000..4b7daf20f --- /dev/null +++ b/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs @@ -0,0 +1,55 @@ +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class M10BetterDataBindings : IStorageMigration + { + private void Migrate(BsonValue bsonValue) + { + if (!bsonValue.IsDocument || !bsonValue.AsDocument.TryGetValue("PropertyEntities", out BsonValue propertyEntities)) + return; + + foreach (BsonValue propertyEntity in propertyEntities.AsArray) + { + if (!propertyEntity.AsDocument.TryGetValue("DataBindingEntities", out BsonValue dataBindingEntities)) + continue; + foreach (BsonValue dataBindingEntity in dataBindingEntities.AsArray) + { + if (!dataBindingEntity.AsDocument.TryGetValue("TargetExpression", out BsonValue targetExpression)) + continue; + string value = targetExpression.AsString; + if (value == "value => value" || value == "b => b") + { + dataBindingEntity.AsDocument["Identifier"] = "Value"; + } + else + { + string selector = value.Split("=>")[1]; + string property = selector.Split(".")[1]; + dataBindingEntity.AsDocument["Identifier"] = property; + } + + dataBindingEntity.AsDocument.Remove("TargetExpression"); + } + } + } + + public int UserVersion => 10; + + public void Apply(LiteRepository repository) + { + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + foreach (BsonDocument bsonDocument in collection.FindAll()) + { + foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray) + Migrate(bsonLayer); + + foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray) + Migrate(bsonLayer); + + collection.Update(bsonDocument); + } + } + } +} \ No newline at end of file From dbdeb7568bbe3b91db86018c4e5d6202ed8b1263 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Mar 2021 19:22:49 +0100 Subject: [PATCH 21/21] Plugins - Fixed double-dipping Disable on Dispose --- src/Artemis.Core/Plugins/PluginFeature.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index 1eadf61aa..f2fb01fad 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -73,10 +73,10 @@ namespace Artemis.Core if (!enable) { - IsEnabled = false; - // Even if disable failed, still leave it in a disabled state to avoid more issues InternalDisable(); + IsEnabled = false; + OnDisabled(); return; } @@ -146,7 +146,7 @@ namespace Artemis.Core { if (disposing) { - Disable(); + InternalDisable(); } }