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:
Robert Beekman authored and GitHub committed 2021-06-03 22:34:43 +02:00
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 =