mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Profiles - Reworked profile system
Sidebar - Redesigned sidebar with customizable categories Profiles - Added the ability to configure custom profile icons Profiles - Added the ability to activate multiple profiles for modules at once Profiles - Added the ability to create profiles for no modules Profiles - Added the ability to suspend a profile or an entire category Profiles - Added profile activation conditions Profiles - Added file-based importing/exporting Profile editor - Condensed UI, removed tabs Profile editor - Disable condition operators until a left-side is picked
This commit is contained in:
1 parent
4152c290d2
commit
ceeaa4bf6d
164 files changed
+5019
-4220
No files matched your search
@@ -0,0 +1,63 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.ProfileConfigurationIcon"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<shared:StreamToBitmapImageConverter x:Key="StreamToBitmapImageConverter" />
|
||||
</UserControl.Resources>
|
||||
<ContentControl>
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Value="{x:Static core:ProfileConfigurationIconType.MaterialIcon}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<materialDesign:PackIcon Kind="{Binding ConfigurationIcon.MaterialIcon, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Width="Auto"
|
||||
Height="Auto" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Value="{x:Static core:ProfileConfigurationIconType.BitmapImage}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Image
|
||||
Source="{Binding ConfigurationIcon.FileIcon, Converter={StaticResource StreamToBitmapImageConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Width="Auto"
|
||||
Height="Auto" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Value="{x:Static core:ProfileConfigurationIconType.SvgImage}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<svgc:SvgViewbox StreamSource="{Binding ConfigurationIcon.FileIcon, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Width="Auto"
|
||||
Height="Auto" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ProfileConfigurationIcon.xaml
|
||||
/// </summary>
|
||||
public partial class ProfileConfigurationIcon : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="PackIconKind" />
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ConfigurationIconProperty =
|
||||
DependencyProperty.Register(nameof(ConfigurationIcon), typeof(Core.ProfileConfigurationIcon), typeof(ProfileConfigurationIcon));
|
||||
|
||||
public ProfileConfigurationIcon()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Core.ProfileConfigurationIcon ConfigurationIcon
|
||||
{
|
||||
get => (Core.ProfileConfigurationIcon) GetValue(ConfigurationIconProperty);
|
||||
set => SetValue(ConfigurationIconProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:Stream" /> into <see cref="T:BitmapImage" />.
|
||||
/// </summary>
|
||||
[ValueConversion(typeof(Stream), typeof(BitmapImage))]
|
||||
public class StreamToBitmapImageConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not Stream stream)
|
||||
return null;
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
BitmapImage selectedBitmap = new();
|
||||
selectedBitmap.BeginInit();
|
||||
selectedBitmap.StreamSource = stream;
|
||||
selectedBitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||
selectedBitmap.EndInit();
|
||||
selectedBitmap.Freeze();
|
||||
|
||||
stream.Position = 0;
|
||||
return selectedBitmap;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return Binding.DoNothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,11 @@
|
||||
</ContextMenu.Resources>
|
||||
<ContextMenu.ItemsSource>
|
||||
<CompositeCollection>
|
||||
<MenuItem Header="No modules available" IsEnabled="False" Visibility="{Binding Data.HasNoModules, Source={StaticResource DataContextProxy}, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="EmojiSad" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<CollectionContainer Collection="{Binding Data.ExtraDataModelViewModels, Source={StaticResource DataContextProxy}}" />
|
||||
<Separator Visibility="{Binding Data.HasExtraDataModels, Source={StaticResource DataContextProxy}, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"/>
|
||||
<CollectionContainer Collection="{Binding Data.DataModelViewModel.Children, Source={StaticResource DataContextProxy}}" />
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Artemis.UI.Shared.Input
|
||||
/// </summary>
|
||||
public class DataModelDynamicViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
private readonly List<Module> _modules;
|
||||
private readonly IDataModelUIService _dataModelUIService;
|
||||
private readonly Module _module;
|
||||
private readonly Timer _updateTimer;
|
||||
private SolidColorBrush _buttonBrush = new(Color.FromRgb(171, 71, 188));
|
||||
private DataModelPath? _dataModelPath;
|
||||
@@ -31,9 +31,9 @@ namespace Artemis.UI.Shared.Input
|
||||
private bool _isEnabled = true;
|
||||
private string _placeholder = "Select a property";
|
||||
|
||||
internal DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService)
|
||||
internal DataModelDynamicViewModel(List<Module> modules, ISettingsService settingsService, IDataModelUIService dataModelUIService)
|
||||
{
|
||||
_module = module;
|
||||
_modules = modules;
|
||||
_dataModelUIService = dataModelUIService;
|
||||
_updateTimer = new Timer(500);
|
||||
|
||||
@@ -107,6 +107,11 @@ namespace Artemis.UI.Shared.Input
|
||||
/// </summary>
|
||||
public BindableCollection<DataModelPropertiesViewModel> ExtraDataModelViewModels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether there are any modules providing data models
|
||||
/// </summary>
|
||||
public bool HasNoModules => (DataModelViewModel == null || !DataModelViewModel.Children.Any()) && !HasExtraDataModels;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether there are any extra data models
|
||||
/// </summary>
|
||||
@@ -233,7 +238,7 @@ namespace Artemis.UI.Shared.Input
|
||||
private void Initialize()
|
||||
{
|
||||
// Get the data models
|
||||
DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_module, true);
|
||||
DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_modules, true);
|
||||
if (DataModelViewModel != null)
|
||||
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
|
||||
ExtraDataModelViewModels.CollectionChanged += ExtraDataModelViewModelsOnCollectionChanged;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on profile related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class ProfileConfigurationEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration, ProfileConfiguration? previousProfileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
PreviousProfileConfiguration = previousProfileConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile the event was raised for
|
||||
/// </summary>
|
||||
public ProfileConfiguration? ProfileConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile before the event was raised
|
||||
/// </summary>
|
||||
public ProfileConfiguration? PreviousProfileConfiguration { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on profile related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class ProfileEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileEventArgs(Profile? profile)
|
||||
{
|
||||
Profile = profile;
|
||||
}
|
||||
|
||||
internal ProfileEventArgs(Profile? profile, Profile? previousProfile)
|
||||
{
|
||||
Profile = profile;
|
||||
PreviousProfile = previousProfile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile the event was raised for
|
||||
/// </summary>
|
||||
public Profile? Profile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile before the event was raised
|
||||
/// </summary>
|
||||
public Profile? PreviousProfile { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Input;
|
||||
@@ -20,9 +21,9 @@ namespace Artemis.UI.Shared
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelDynamicViewModel" /> class
|
||||
/// </summary>
|
||||
/// <param name="module">The module to associate the dynamic view model with</param>
|
||||
/// <param name="modules">The modules to associate the dynamic view model with</param>
|
||||
/// <returns>A new instance of the <see cref="DataModelDynamicViewModel" /> class</returns>
|
||||
DataModelDynamicViewModel DataModelDynamicViewModel(Module module);
|
||||
DataModelDynamicViewModel DataModelDynamicViewModel(List<Module> modules);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelStaticViewModel" /> class
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Artemis.UI.Shared
|
||||
if (InputDragging)
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
else
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
ProfileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ namespace Artemis.UI.Shared
|
||||
public void InputDragEnded(object sender, EventArgs e)
|
||||
{
|
||||
InputDragging = false;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
ProfileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
|
||||
private void LayerPropertyOnUpdated(object? sender, EventArgs e)
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Artemis.UI.Shared.Services
|
||||
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
||||
{
|
||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name))
|
||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion).OrderBy(d => d.DataModelDescription.Name))
|
||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||
|
||||
// Update to populate children
|
||||
@@ -47,7 +47,7 @@ namespace Artemis.UI.Shared.Services
|
||||
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
|
||||
{
|
||||
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children
|
||||
.Where(d => d.DataModel != null && !d.DataModel.Feature.IsEnabled)
|
||||
.Where(d => d.DataModel != null && !d.DataModel.Module.IsEnabled)
|
||||
.ToList();
|
||||
foreach (DataModelVisualizationViewModel child in disabledChildren)
|
||||
mainDataModelVisualization.Children.Remove(child);
|
||||
@@ -65,34 +65,33 @@ namespace Artemis.UI.Shared.Services
|
||||
mainDataModelVisualization.Update(this, null);
|
||||
}
|
||||
|
||||
public DataModelPropertiesViewModel? GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel)
|
||||
public DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel)
|
||||
{
|
||||
DataModelPropertiesViewModel root;
|
||||
// This will contain any modules that are always available
|
||||
if (includeMainDataModel)
|
||||
root = GetMainDataModelVisualization();
|
||||
else
|
||||
{
|
||||
DataModelPropertiesViewModel mainDataModel = GetMainDataModelVisualization();
|
||||
|
||||
// If the main data model already includes the plugin data model we're done
|
||||
if (mainDataModel.Children.Any(c => c.DataModel?.Feature == pluginFeature))
|
||||
return mainDataModel;
|
||||
// Otherwise get just the plugin data model and add it
|
||||
DataModelPropertiesViewModel? pluginDataModel = GetPluginDataModelVisualization(pluginFeature, false);
|
||||
if (pluginDataModel != null)
|
||||
mainDataModel.Children.Add(pluginDataModel);
|
||||
|
||||
return mainDataModel;
|
||||
root = new DataModelPropertiesViewModel(null, null, null);
|
||||
root.UpdateRequested += (sender, args) => root.Update(this, null);
|
||||
}
|
||||
|
||||
DataModel? dataModel = _dataModelService.GetPluginDataModel(pluginFeature);
|
||||
if (dataModel == null)
|
||||
foreach (Module module in modules)
|
||||
{
|
||||
DataModel? dataModel = _dataModelService.GetPluginDataModel(module);
|
||||
if (dataModel == null)
|
||||
continue;
|
||||
|
||||
root.Children.Add(new DataModelPropertiesViewModel(dataModel, root, new DataModelPath(dataModel)));
|
||||
}
|
||||
|
||||
if (!root.Children.Any())
|
||||
return null;
|
||||
|
||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, new DataModelPath(dataModel)));
|
||||
|
||||
// Update to populate children
|
||||
viewModel.Update(this, null);
|
||||
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
|
||||
return viewModel;
|
||||
root.Update(this, null);
|
||||
return root;
|
||||
}
|
||||
|
||||
public DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type>? compatibleConversionTypes = null) where T : DataModelInputViewModel
|
||||
@@ -226,9 +225,14 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
}
|
||||
|
||||
public DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module)
|
||||
public DataModelDynamicViewModel GetDynamicSelectionViewModel(Module? module)
|
||||
{
|
||||
return _dataModelVmFactory.DataModelDynamicViewModel(module);
|
||||
return _dataModelVmFactory.DataModelDynamicViewModel(module == null ? new List<Module>() : new List<Module> {module});
|
||||
}
|
||||
|
||||
public DataModelDynamicViewModel GetDynamicSelectionViewModel(List<Module> modules)
|
||||
{
|
||||
return _dataModelVmFactory.DataModelDynamicViewModel(modules);
|
||||
}
|
||||
|
||||
public DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription)
|
||||
|
||||
@@ -34,10 +34,13 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <summary>
|
||||
/// Creates a data model visualization view model for the data model of the provided plugin feature
|
||||
/// </summary>
|
||||
/// <param name="pluginFeature">The plugin feature to create hte data model visualization view model for</param>
|
||||
/// <param name="includeMainDataModel">Whether or not also to include the main data model</param>
|
||||
/// <param name="modules">The modules to create the data model visualization view model for</param>
|
||||
/// <param name="includeMainDataModel">
|
||||
/// Whether or not also to include the main data model (and therefore any modules marked
|
||||
/// as <see cref="Module.IsAlwaysAvailable" />)
|
||||
/// </param>
|
||||
/// <returns>A data model visualization view model containing the data model of the provided feature</returns>
|
||||
DataModelPropertiesViewModel? GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel);
|
||||
DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the children of the provided main data model visualization, removing disabled children and adding newly
|
||||
@@ -105,9 +108,16 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <summary>
|
||||
/// Creates a view model that allows selecting a value from the data model
|
||||
/// </summary>
|
||||
/// <param name="module"></param>
|
||||
/// <param name="module">An extra non-always active module to include</param>
|
||||
/// <returns></returns>
|
||||
DataModelDynamicViewModel GetDynamicSelectionViewModel(Module? module);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a view model that allows selecting a value from the data model
|
||||
/// </summary>
|
||||
/// <param name="modules">A list of extra extra non-always active modules to include</param>
|
||||
/// <returns>A view model that allows selecting a value from the data model</returns>
|
||||
DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module);
|
||||
DataModelDynamicViewModel GetDynamicSelectionViewModel(List<Module> modules);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a view model that allows entering a value matching the target data model type
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <param name="confirmText">The text of the confirm button, defaults to "Confirm"</param>
|
||||
/// <param name="cancelText">The text of the cancel button, defaults to "Cancel"</param>
|
||||
/// <returns>A task that resolves to true if confirmed and false if cancelled</returns>
|
||||
Task<bool> ShowConfirmDialog(string header, string text, string confirmText = "Confirm", string cancelText = "Cancel");
|
||||
Task<bool> ShowConfirmDialog(string header, string text, string confirmText = "Confirm", string? cancelText = "Cancel");
|
||||
|
||||
/// <summary>
|
||||
/// Shows a confirm dialog on the dialog host provided in identifier.
|
||||
@@ -33,7 +33,7 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <param name="confirmText">The text of the confirm button, defaults to "Confirm"</param>
|
||||
/// <param name="cancelText">The text of the cancel button, defaults to "Cancel"</param>
|
||||
/// <returns>A task that resolves to true if confirmed and false if cancelled</returns>
|
||||
Task<bool> ShowConfirmDialogAt(string identifier, string header, string text, string confirmText = "Confirm", string cancelText = "Cancel");
|
||||
Task<bool> ShowConfirmDialogAt(string identifier, string header, string text, string confirmText = "Confirm", string? cancelText = "Cancel");
|
||||
|
||||
/// <summary>
|
||||
/// Shows a dialog by initializing a view model implementing <see cref="DialogViewModelBase" />
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.UI.Shared.Services
|
||||
{
|
||||
@@ -12,8 +11,15 @@ namespace Artemis.UI.Shared.Services
|
||||
/// </summary>
|
||||
public interface IProfileEditorService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the currently selected profile configuration
|
||||
/// <para><see langword="null" /> if the editor is closed</para>
|
||||
/// </summary>
|
||||
ProfileConfiguration? SelectedProfileConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently selected profile
|
||||
/// <para><see langword="null" /> if the editor is closed, always equal to <see cref="SelectedProfileConfiguration" />.<see cref="Profile" /></para>
|
||||
/// </summary>
|
||||
Profile? SelectedProfile { get; }
|
||||
|
||||
@@ -48,15 +54,15 @@ namespace Artemis.UI.Shared.Services
|
||||
bool Playing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the selected profile
|
||||
/// Changes the selected profile by its <see cref="ProfileConfiguration" />
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile to select</param>
|
||||
void ChangeSelectedProfile(Profile? profile);
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to select</param>
|
||||
void ChangeSelectedProfileConfiguration(ProfileConfiguration? profileConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the selected profile and saves it to persistent storage
|
||||
/// Saves the <see cref="Profile" /> of the selected <see cref="ProfileConfiguration" /> to persistent storage
|
||||
/// </summary>
|
||||
void UpdateSelectedProfile();
|
||||
void SaveSelectedProfileConfiguration();
|
||||
|
||||
/// <summary>
|
||||
/// Changes the selected profile element
|
||||
@@ -65,9 +71,9 @@ namespace Artemis.UI.Shared.Services
|
||||
void ChangeSelectedProfileElement(RenderProfileElement? profileElement);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the selected profile element and saves the profile it is contained in to persistent storage
|
||||
/// Saves the currently selected <see cref="ProfileElement" /> to persistent storage
|
||||
/// </summary>
|
||||
void UpdateSelectedProfileElement();
|
||||
void SaveSelectedProfileElement();
|
||||
|
||||
/// <summary>
|
||||
/// Changes the selected data binding property
|
||||
@@ -81,22 +87,16 @@ namespace Artemis.UI.Shared.Services
|
||||
void UpdateProfilePreview();
|
||||
|
||||
/// <summary>
|
||||
/// Restores the profile to the last <see cref="UpdateSelectedProfile" /> call
|
||||
/// Restores the profile to the last <see cref="SaveSelectedProfileConfiguration" /> call
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if undo was successful, otherwise <see langword="false" /></returns>
|
||||
bool UndoUpdateProfile();
|
||||
bool UndoSaveProfile();
|
||||
|
||||
/// <summary>
|
||||
/// Restores the profile to the last <see cref="UndoUpdateProfile" /> call
|
||||
/// Restores the profile to the last <see cref="UndoSaveProfile" /> call
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if redo was successful, otherwise <see langword="false" /></returns>
|
||||
bool RedoUpdateProfile();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current module the profile editor is initialized for
|
||||
/// </summary>
|
||||
/// <returns>The current module the profile editor is initialized for</returns>
|
||||
ProfileModule? GetCurrentModule();
|
||||
bool RedoSaveProfile();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
||||
@@ -182,22 +182,22 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <summary>
|
||||
/// Occurs when a new profile is selected
|
||||
/// </summary>
|
||||
event EventHandler<ProfileEventArgs> ProfileSelected;
|
||||
event EventHandler<ProfileConfigurationEventArgs> SelectedProfileChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs then the currently selected profile is updated
|
||||
/// </summary>
|
||||
event EventHandler<ProfileEventArgs> SelectedProfileUpdated;
|
||||
event EventHandler<ProfileConfigurationEventArgs> SelectedProfileSaved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile element is selected
|
||||
/// </summary>
|
||||
event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;
|
||||
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected profile element is updated
|
||||
/// </summary>
|
||||
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementUpdated;
|
||||
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementSaved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected data binding layer property is changed
|
||||
|
||||
@@ -24,70 +24,25 @@ namespace Artemis.UI.Shared.Services
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IModuleService _moduleService;
|
||||
private readonly object _selectedProfileElementLock = new();
|
||||
private readonly object _selectedProfileLock = new();
|
||||
private TimeSpan _currentTime;
|
||||
private bool _doTick;
|
||||
private int _pixelsPerSecond;
|
||||
|
||||
public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService)
|
||||
public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService, IModuleService moduleService)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_logger = logger;
|
||||
_profileService = profileService;
|
||||
_rgbService = rgbService;
|
||||
_moduleService = moduleService;
|
||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||
coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||
PixelsPerSecond = 100;
|
||||
}
|
||||
|
||||
public event EventHandler? CurrentTimelineChanged;
|
||||
|
||||
protected virtual void OnSelectedProfileChanged(ProfileEventArgs e)
|
||||
{
|
||||
ProfileSelected?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedProfileUpdated(ProfileEventArgs e)
|
||||
{
|
||||
SelectedProfileUpdated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedProfileElementChanged(RenderProfileElementEventArgs e)
|
||||
{
|
||||
ProfileElementSelected?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedProfileElementUpdated(RenderProfileElementEventArgs e)
|
||||
{
|
||||
SelectedProfileElementUpdated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnCurrentTimeChanged()
|
||||
{
|
||||
CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnCurrentTimelineChanged()
|
||||
{
|
||||
CurrentTimelineChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnPixelsPerSecondChanged()
|
||||
{
|
||||
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnProfilePreviewUpdated()
|
||||
{
|
||||
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedDataBindingChanged()
|
||||
{
|
||||
SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
|
||||
{
|
||||
if (!_doTick) return;
|
||||
@@ -101,7 +56,7 @@ namespace Artemis.UI.Shared.Services
|
||||
return;
|
||||
|
||||
// Trigger a profile change
|
||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
|
||||
OnSelectedProfileChanged(new ProfileConfigurationEventArgs(SelectedProfileConfiguration, SelectedProfileConfiguration));
|
||||
// Trigger a selected element change
|
||||
RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement;
|
||||
if (SelectedProfileElement is Folder folder)
|
||||
@@ -148,16 +103,11 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectedProfileOnDeactivated(object? sender, EventArgs e)
|
||||
{
|
||||
// Execute.PostToUIThread(() => ChangeSelectedProfile(null));
|
||||
ChangeSelectedProfile(null);
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
|
||||
|
||||
public bool Playing { get; set; }
|
||||
public Profile? SelectedProfile { get; private set; }
|
||||
public ProfileConfiguration? SelectedProfileConfiguration { get; private set; }
|
||||
public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile;
|
||||
public RenderProfileElement? SelectedProfileElement { get; private set; }
|
||||
public ILayerProperty? SelectedDataBinding { get; private set; }
|
||||
|
||||
@@ -183,43 +133,54 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeSelectedProfile(Profile? profile)
|
||||
public void ChangeSelectedProfileConfiguration(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
lock (_selectedProfileLock)
|
||||
{
|
||||
if (SelectedProfile == profile)
|
||||
if (SelectedProfileConfiguration == profileConfiguration)
|
||||
return;
|
||||
|
||||
if (profile != null && !profile.IsActivated)
|
||||
throw new ArtemisSharedUIException("Cannot change the selected profile to an inactive profile");
|
||||
if (profileConfiguration?.Profile != null && profileConfiguration.Profile.Disposed)
|
||||
throw new ArtemisSharedUIException("Cannot select a disposed profile");
|
||||
|
||||
_logger.Verbose("ChangeSelectedProfile {profile}", profile);
|
||||
_logger.Verbose("ChangeSelectedProfileConfiguration {profile}", profileConfiguration);
|
||||
ChangeSelectedProfileElement(null);
|
||||
ProfileConfigurationEventArgs profileConfigurationElementEvent = new(profileConfiguration, SelectedProfileConfiguration);
|
||||
|
||||
ProfileEventArgs profileElementEvent = new(profile, SelectedProfile);
|
||||
// No need to deactivate the profile, if needed it will be deactivated next update
|
||||
if (SelectedProfileConfiguration != null)
|
||||
SelectedProfileConfiguration.IsBeingEdited = false;
|
||||
|
||||
// Ensure there is never a deactivated profile as the selected profile
|
||||
if (SelectedProfile != null)
|
||||
SelectedProfile.Deactivated -= SelectedProfileOnDeactivated;
|
||||
SelectedProfile = profile;
|
||||
if (SelectedProfile != null)
|
||||
SelectedProfile.Deactivated += SelectedProfileOnDeactivated;
|
||||
// The new profile may need activation
|
||||
SelectedProfileConfiguration = profileConfiguration;
|
||||
if (SelectedProfileConfiguration != null)
|
||||
{
|
||||
SelectedProfileConfiguration.IsBeingEdited = true;
|
||||
_moduleService.SetActivationOverride(SelectedProfileConfiguration.Module);
|
||||
_profileService.ActivateProfile(SelectedProfileConfiguration);
|
||||
_profileService.RenderForEditor = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_moduleService.SetActivationOverride(null);
|
||||
_profileService.RenderForEditor = false;
|
||||
}
|
||||
|
||||
OnSelectedProfileChanged(profileElementEvent);
|
||||
OnSelectedProfileChanged(profileConfigurationElementEvent);
|
||||
UpdateProfilePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSelectedProfile()
|
||||
public void SaveSelectedProfileConfiguration()
|
||||
{
|
||||
lock (_selectedProfileLock)
|
||||
{
|
||||
_logger.Verbose("UpdateSelectedProfile {profile}", SelectedProfile);
|
||||
_logger.Verbose("SaveSelectedProfileConfiguration {profile}", SelectedProfile);
|
||||
if (SelectedProfile == null)
|
||||
return;
|
||||
|
||||
_profileService.UpdateProfile(SelectedProfile, true);
|
||||
OnSelectedProfileUpdated(new ProfileEventArgs(SelectedProfile));
|
||||
_profileService.SaveProfile(SelectedProfile, true);
|
||||
OnSelectedProfileUpdated(new ProfileConfigurationEventArgs(SelectedProfileConfiguration));
|
||||
UpdateProfilePreview();
|
||||
}
|
||||
}
|
||||
@@ -240,15 +201,15 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSelectedProfileElement()
|
||||
public void SaveSelectedProfileElement()
|
||||
{
|
||||
lock (_selectedProfileElementLock)
|
||||
{
|
||||
_logger.Verbose("UpdateSelectedProfileElement {profile}", SelectedProfileElement);
|
||||
_logger.Verbose("SaveSelectedProfileElement {profile}", SelectedProfileElement);
|
||||
if (SelectedProfile == null)
|
||||
return;
|
||||
|
||||
_profileService.UpdateProfile(SelectedProfile, true);
|
||||
_profileService.SaveProfile(SelectedProfile, true);
|
||||
OnSelectedProfileElementUpdated(new RenderProfileElementEventArgs(SelectedProfileElement));
|
||||
UpdateProfilePreview();
|
||||
}
|
||||
@@ -267,12 +228,12 @@ namespace Artemis.UI.Shared.Services
|
||||
Tick();
|
||||
}
|
||||
|
||||
public bool UndoUpdateProfile()
|
||||
public bool UndoSaveProfile()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
return false;
|
||||
|
||||
bool undid = _profileService.UndoUpdateProfile(SelectedProfile);
|
||||
bool undid = _profileService.UndoSaveProfile(SelectedProfile);
|
||||
if (!undid)
|
||||
return false;
|
||||
|
||||
@@ -280,12 +241,12 @@ namespace Artemis.UI.Shared.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RedoUpdateProfile()
|
||||
public bool RedoSaveProfile()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
return false;
|
||||
|
||||
bool redid = _profileService.RedoUpdateProfile(SelectedProfile);
|
||||
bool redid = _profileService.RedoSaveProfile(SelectedProfile);
|
||||
if (!redid)
|
||||
return false;
|
||||
|
||||
@@ -319,8 +280,11 @@ namespace Artemis.UI.Shared.Services
|
||||
if (existing != null)
|
||||
{
|
||||
if (existing.Plugin != plugin)
|
||||
{
|
||||
throw new ArtemisSharedUIException($"Cannot register property editor for type {supportedType.Name} because an editor was already " +
|
||||
$"registered by {existing.Plugin}");
|
||||
}
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
@@ -364,8 +328,10 @@ namespace Artemis.UI.Shared.Services
|
||||
|
||||
if (snapToCurrentTime)
|
||||
// Snap to the current time
|
||||
{
|
||||
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||
return CurrentTime;
|
||||
}
|
||||
|
||||
if (snapTimes != null)
|
||||
{
|
||||
@@ -401,13 +367,9 @@ namespace Artemis.UI.Shared.Services
|
||||
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
||||
}
|
||||
else if (registration != null)
|
||||
{
|
||||
viewModelType = registration.ViewModelType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (viewModelType == null)
|
||||
return null;
|
||||
@@ -419,11 +381,6 @@ namespace Artemis.UI.Shared.Services
|
||||
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
|
||||
}
|
||||
|
||||
public ProfileModule? GetCurrentModule()
|
||||
{
|
||||
return SelectedProfile?.Module;
|
||||
}
|
||||
|
||||
public List<ArtemisLed> GetLedsInRectangle(Rect rect)
|
||||
{
|
||||
return _rgbService.EnabledDevices
|
||||
@@ -432,15 +389,6 @@ namespace Artemis.UI.Shared.Services
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public event EventHandler<ProfileEventArgs>? ProfileSelected;
|
||||
public event EventHandler<ProfileEventArgs>? SelectedProfileUpdated;
|
||||
public event EventHandler<RenderProfileElementEventArgs>? ProfileElementSelected;
|
||||
public event EventHandler<RenderProfileElementEventArgs>? SelectedProfileElementUpdated;
|
||||
public event EventHandler? SelectedDataBindingChanged;
|
||||
public event EventHandler? CurrentTimeChanged;
|
||||
public event EventHandler? PixelsPerSecondChanged;
|
||||
public event EventHandler? ProfilePreviewUpdated;
|
||||
|
||||
#region Copy/paste
|
||||
|
||||
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
|
||||
@@ -512,7 +460,7 @@ namespace Artemis.UI.Shared.Services
|
||||
if (pasted != null)
|
||||
{
|
||||
target.Profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
UpdateSelectedProfile();
|
||||
SaveSelectedProfileConfiguration();
|
||||
ChangeSelectedProfileElement(pasted);
|
||||
}
|
||||
|
||||
@@ -520,5 +468,58 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? SelectedProfileChanged;
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? SelectedProfileSaved;
|
||||
public event EventHandler<RenderProfileElementEventArgs>? SelectedProfileElementChanged;
|
||||
public event EventHandler<RenderProfileElementEventArgs>? SelectedProfileElementSaved;
|
||||
public event EventHandler? SelectedDataBindingChanged;
|
||||
public event EventHandler? CurrentTimeChanged;
|
||||
public event EventHandler? PixelsPerSecondChanged;
|
||||
public event EventHandler? ProfilePreviewUpdated;
|
||||
|
||||
protected virtual void OnSelectedProfileChanged(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
SelectedProfileChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedProfileUpdated(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
SelectedProfileSaved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedProfileElementChanged(RenderProfileElementEventArgs e)
|
||||
{
|
||||
SelectedProfileElementChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedProfileElementUpdated(RenderProfileElementEventArgs e)
|
||||
{
|
||||
SelectedProfileElementSaved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnCurrentTimeChanged()
|
||||
{
|
||||
CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnPixelsPerSecondChanged()
|
||||
{
|
||||
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnProfilePreviewUpdated()
|
||||
{
|
||||
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedDataBindingChanged()
|
||||
{
|
||||
SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
internal class BindingProxy : Freezable
|
||||
public class BindingProxy : Freezable
|
||||
{
|
||||
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty DataProperty =
|
||||
|
||||
Reference in new issue
Block a user