mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +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:
parent
4152c290d2
commit
ceeaa4bf6d
@ -1,5 +1,6 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofileconfiguration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaptionhints/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaptionhints/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption_005Chints/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption_005Chints/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
21
src/Artemis.Core/Events/ModuleEventArgs.cs
Normal file
21
src/Artemis.Core/Events/ModuleEventArgs.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data about module events
|
||||||
|
/// </summary>
|
||||||
|
public class ModuleEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
internal ModuleEventArgs(Module module)
|
||||||
|
{
|
||||||
|
Module = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the module this event is related to
|
||||||
|
/// </summary>
|
||||||
|
public Module Module { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data for profile configuration events.
|
||||||
|
/// </summary>
|
||||||
|
public class ProfileConfigurationEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
internal ProfileConfigurationEventArgs(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
ProfileConfiguration = profileConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile configuration this event is related to
|
||||||
|
/// </summary>
|
||||||
|
public ProfileConfiguration ProfileConfiguration { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/Artemis.Core/JsonConverters/StreamConverter.cs
Normal file
45
src/Artemis.Core/JsonConverters/StreamConverter.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Artemis.Core.JsonConverters
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class StreamConverter : JsonConverter<Stream>
|
||||||
|
{
|
||||||
|
#region Overrides of JsonConverter<Stream>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void WriteJson(JsonWriter writer, Stream? value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
writer.WriteNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using MemoryStream memoryStream = new();
|
||||||
|
value.Position = 0;
|
||||||
|
value.CopyTo(memoryStream);
|
||||||
|
writer.WriteValue(memoryStream.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Stream? ReadJson(JsonReader reader, Type objectType, Stream? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (reader.Value is not string base64)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (existingValue == null || !hasExistingValue || !existingValue.CanRead)
|
||||||
|
return new MemoryStream(Convert.FromBase64String(base64));
|
||||||
|
|
||||||
|
using MemoryStream memoryStream = new(Convert.FromBase64String(base64));
|
||||||
|
existingValue.Position = 0;
|
||||||
|
memoryStream.CopyTo(existingValue);
|
||||||
|
existingValue.Position = 0;
|
||||||
|
return existingValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -105,10 +105,19 @@ namespace Artemis.Core
|
|||||||
InitializeRightPath();
|
InitializeRightPath();
|
||||||
// Right side static
|
// Right side static
|
||||||
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (LeftPath != null && LeftPath.IsValid)
|
// If the left path is not valid we cannot reliably set up the right side because the type is unknown
|
||||||
|
// Because of that wait for it to validate first
|
||||||
|
if (LeftPath != null && !LeftPath.IsValid)
|
||||||
{
|
{
|
||||||
|
LeftPath.PathValidated += InitializeRightSideStatic;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LeftPath == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Use the left side type so JSON.NET has a better idea what to do
|
// Use the left side type so JSON.NET has a better idea what to do
|
||||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||||
object? rightSideValue;
|
object? rightSideValue;
|
||||||
@ -126,17 +135,37 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
UpdateRightSideStatic(rightSideValue);
|
UpdateRightSideStatic(rightSideValue);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Hope for the best...
|
|
||||||
UpdateRightSideStatic(CoreJson.DeserializeObject(Entity.RightStaticValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (JsonReaderException e)
|
catch (JsonReaderException e)
|
||||||
{
|
{
|
||||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRightSideStatic(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
if (LeftPath == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LeftPath.PathValidated -= InitializeRightSideStatic;
|
||||||
|
|
||||||
|
// Use the left side type so JSON.NET has a better idea what to do
|
||||||
|
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||||
|
object? rightSideValue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||||
|
}
|
||||||
|
// If deserialization fails, use the type's default
|
||||||
|
catch (JsonSerializationException e)
|
||||||
|
{
|
||||||
|
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||||
|
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateRightSideStatic(rightSideValue);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the left path of this condition predicate
|
/// Initializes the left path of this condition predicate
|
||||||
|
|||||||
@ -16,7 +16,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
internal EventPredicateWrapperDataModel()
|
internal EventPredicateWrapperDataModel()
|
||||||
{
|
{
|
||||||
Feature = Constants.CorePluginFeature;
|
Module = Constants.CorePluginFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
internal ListPredicateWrapperDataModel()
|
internal ListPredicateWrapperDataModel()
|
||||||
{
|
{
|
||||||
Feature = Constants.CorePluginFeature;
|
Module = Constants.CorePluginFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -93,7 +93,7 @@ namespace Artemis.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data model ID of the <see cref="Target" /> if it is a <see cref="DataModel" />
|
/// Gets the data model ID of the <see cref="Target" /> if it is a <see cref="DataModel" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? DataModelId => Target?.Feature.Id;
|
public string? DataModelId => Target?.Module.Id;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the point-separated path associated with this <see cref="DataModelPath" />
|
/// Gets the point-separated path associated with this <see cref="DataModelPath" />
|
||||||
@ -327,7 +327,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
|
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
|
||||||
{
|
{
|
||||||
if (e.Registration.DataModel.Feature.Id != Entity.DataModelId)
|
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Target = e.Registration.DataModel;
|
Target = e.Registration.DataModel;
|
||||||
@ -336,7 +336,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
|
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
|
||||||
{
|
{
|
||||||
if (e.Registration.DataModel.Feature.Id != Entity.DataModelId)
|
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Target = null;
|
Target = null;
|
||||||
|
|||||||
@ -205,13 +205,6 @@ namespace Artemis.Core
|
|||||||
canvas.SaveLayer(layerPaint);
|
canvas.SaveLayer(layerPaint);
|
||||||
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
||||||
|
|
||||||
// If required, apply the opacity override of the module to the root folder
|
|
||||||
if (IsRootFolder && Profile.Module.OpacityOverride < 1)
|
|
||||||
{
|
|
||||||
double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride);
|
|
||||||
layerPaint.Color = layerPaint.Color.WithAlpha((byte) (layerPaint.Color.Alpha * multiplier));
|
|
||||||
}
|
|
||||||
|
|
||||||
// No point rendering if the alpha was set to zero by one of the effects
|
// No point rendering if the alpha was set to zero by one of the effects
|
||||||
if (layerPaint.Color.Alpha == 0)
|
if (layerPaint.Color.Alpha == 0)
|
||||||
return;
|
return;
|
||||||
@ -240,6 +233,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
|
|
||||||
|
Disable();
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Dispose();
|
profileElement.Dispose();
|
||||||
|
|
||||||
|
|||||||
@ -168,6 +168,8 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
Disable();
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
|
|
||||||
// Brush first in case it depends on any of the other disposables during it's own disposal
|
// Brush first in case it depends on any of the other disposables during it's own disposal
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
@ -13,31 +12,15 @@ namespace Artemis.Core
|
|||||||
public sealed class Profile : ProfileElement
|
public sealed class Profile : ProfileElement
|
||||||
{
|
{
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
private bool _isActivated;
|
|
||||||
private bool _isFreshImport;
|
private bool _isFreshImport;
|
||||||
|
|
||||||
internal Profile(ProfileModule module, string name) : base(null!)
|
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
|
||||||
{
|
|
||||||
ProfileEntity = new ProfileEntity();
|
|
||||||
EntityId = Guid.NewGuid();
|
|
||||||
|
|
||||||
Profile = this;
|
|
||||||
Module = module;
|
|
||||||
Name = name;
|
|
||||||
UndoStack = new Stack<string>();
|
|
||||||
RedoStack = new Stack<string>();
|
|
||||||
|
|
||||||
Folder _ = new(this, "Root folder");
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Profile(ProfileModule module, ProfileEntity profileEntity) : base(null!)
|
|
||||||
{
|
{
|
||||||
|
Configuration = configuration;
|
||||||
Profile = this;
|
Profile = this;
|
||||||
ProfileEntity = profileEntity;
|
ProfileEntity = profileEntity;
|
||||||
EntityId = profileEntity.Id;
|
EntityId = profileEntity.Id;
|
||||||
|
|
||||||
Module = module;
|
|
||||||
UndoStack = new Stack<string>();
|
UndoStack = new Stack<string>();
|
||||||
RedoStack = new Stack<string>();
|
RedoStack = new Stack<string>();
|
||||||
|
|
||||||
@ -45,18 +28,9 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the module backing this profile
|
/// Gets the profile configuration of this profile
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ProfileModule Module { get; }
|
public ProfileConfiguration Configuration { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean indicating whether this profile is activated
|
|
||||||
/// </summary>
|
|
||||||
public bool IsActivated
|
|
||||||
{
|
|
||||||
get => _isActivated;
|
|
||||||
private set => SetAndNotify(ref _isActivated, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
|
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
|
||||||
@ -87,8 +61,6 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
if (!IsActivated)
|
|
||||||
throw new ArtemisCoreException($"Cannot update inactive profile: {this}");
|
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Update(deltaTime);
|
profileElement.Update(deltaTime);
|
||||||
@ -102,8 +74,6 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
if (!IsActivated)
|
|
||||||
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
|
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Render(canvas, basePosition);
|
profileElement.Render(canvas, basePosition);
|
||||||
@ -133,7 +103,7 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"[Profile] {nameof(Name)}: {Name}, {nameof(IsActivated)}: {IsActivated}, {nameof(Module)}: {Module}";
|
return $"[Profile] {nameof(Name)}: {Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -149,29 +119,15 @@ namespace Artemis.Core
|
|||||||
layer.PopulateLeds(devices);
|
layer.PopulateLeds(devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the profile has been activated.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? Activated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the profile is being deactivated.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? Deactivated;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!disposing)
|
if (!disposing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OnDeactivating();
|
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Dispose();
|
profileElement.Dispose();
|
||||||
ChildrenList.Clear();
|
ChildrenList.Clear();
|
||||||
|
|
||||||
IsActivated = false;
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +136,7 @@ namespace Artemis.Core
|
|||||||
if (Disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
Name = ProfileEntity.Name;
|
Name = Configuration.Name;
|
||||||
IsFreshImport = ProfileEntity.IsFreshImport;
|
IsFreshImport = ProfileEntity.IsFreshImport;
|
||||||
|
|
||||||
lock (ChildrenList)
|
lock (ChildrenList)
|
||||||
@ -197,9 +153,11 @@ namespace Artemis.Core
|
|||||||
Folder _ = new(this, "Root folder");
|
Folder _ = new(this, "Root folder");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
AddChild(new Folder(this, this, rootFolder));
|
AddChild(new Folder(this, this, rootFolder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal override void Save()
|
internal override void Save()
|
||||||
{
|
{
|
||||||
@ -207,9 +165,7 @@ namespace Artemis.Core
|
|||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
ProfileEntity.Id = EntityId;
|
ProfileEntity.Id = EntityId;
|
||||||
ProfileEntity.ModuleId = Module.Id;
|
ProfileEntity.Name = Configuration.Name;
|
||||||
ProfileEntity.Name = Name;
|
|
||||||
ProfileEntity.IsActive = IsActivated;
|
|
||||||
ProfileEntity.IsFreshImport = IsFreshImport;
|
ProfileEntity.IsFreshImport = IsFreshImport;
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
@ -221,30 +177,5 @@ namespace Artemis.Core
|
|||||||
ProfileEntity.Layers.Clear();
|
ProfileEntity.Layers.Clear();
|
||||||
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
|
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Activate(IEnumerable<ArtemisDevice> devices)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (Disposed)
|
|
||||||
throw new ObjectDisposedException("Profile");
|
|
||||||
if (IsActivated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
PopulateLeds(devices);
|
|
||||||
OnActivated();
|
|
||||||
IsActivated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnActivated()
|
|
||||||
{
|
|
||||||
Activated?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDeactivating()
|
|
||||||
{
|
|
||||||
Deactivated?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
244
src/Artemis.Core/Models/Profile/ProfileCategory.cs
Normal file
244
src/Artemis.Core/Models/Profile/ProfileCategory.cs
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||||
|
{
|
||||||
|
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
||||||
|
private bool _isCollapsed;
|
||||||
|
private bool _isSuspended;
|
||||||
|
private string _name;
|
||||||
|
private int _order;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ProfileCategory" /> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the category</param>
|
||||||
|
internal ProfileCategory(string name)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
Entity = new ProfileCategoryEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ProfileCategory(ProfileCategoryEntity entity)
|
||||||
|
{
|
||||||
|
Entity = entity;
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the profile category
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => SetAndNotify(ref _name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The order in which this category appears in the update loop and sidebar
|
||||||
|
/// </summary>
|
||||||
|
public int Order
|
||||||
|
{
|
||||||
|
get => _order;
|
||||||
|
set => SetAndNotify(ref _order, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether the category is collapsed or not
|
||||||
|
/// <para>Note: Has no implications other than inside the UI</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCollapsed
|
||||||
|
{
|
||||||
|
get => _isCollapsed;
|
||||||
|
set => SetAndNotify(ref _isCollapsed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether this category is suspended, disabling all its profiles
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuspended
|
||||||
|
{
|
||||||
|
get => _isSuspended;
|
||||||
|
set => SetAndNotify(ref _isSuspended, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read only collection of the profiles inside this category
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations => _profileConfigurations.AsReadOnly();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique ID of this category
|
||||||
|
/// </summary>
|
||||||
|
public Guid EntityId => Entity.Id;
|
||||||
|
|
||||||
|
internal ProfileCategoryEntity Entity { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a profile configuration to this category
|
||||||
|
/// </summary>
|
||||||
|
public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
|
||||||
|
{
|
||||||
|
// Removing the original will shift every item in the list forwards, keep that in mind with the target index
|
||||||
|
if (configuration.Category == this && targetIndex != null && targetIndex.Value > _profileConfigurations.IndexOf(configuration))
|
||||||
|
targetIndex -= 1;
|
||||||
|
|
||||||
|
configuration.Category.RemoveProfileConfiguration(configuration);
|
||||||
|
|
||||||
|
if (targetIndex != null)
|
||||||
|
_profileConfigurations.Insert(Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count), configuration);
|
||||||
|
else
|
||||||
|
_profileConfigurations.Add(configuration);
|
||||||
|
configuration.Category = this;
|
||||||
|
|
||||||
|
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||||
|
_profileConfigurations[index].Order = index;
|
||||||
|
OnProfileConfigurationAdded(new ProfileConfigurationEventArgs(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[ProfileCategory] {Order} {nameof(Name)}: {Name}, {nameof(IsSuspended)}: {IsSuspended}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveProfileConfiguration(ProfileConfiguration configuration)
|
||||||
|
{
|
||||||
|
if (!_profileConfigurations.Remove(configuration)) return;
|
||||||
|
|
||||||
|
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||||
|
_profileConfigurations[index].Order = index;
|
||||||
|
OnProfileConfigurationRemoved(new ProfileConfigurationEventArgs(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IStorageModel
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
Name = Entity.Name;
|
||||||
|
IsCollapsed = Entity.IsCollapsed;
|
||||||
|
IsSuspended = Entity.IsSuspended;
|
||||||
|
Order = Entity.Order;
|
||||||
|
|
||||||
|
_profileConfigurations.Clear();
|
||||||
|
foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations)
|
||||||
|
_profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
Entity.Name = Name;
|
||||||
|
Entity.IsCollapsed = IsCollapsed;
|
||||||
|
Entity.IsSuspended = IsSuspended;
|
||||||
|
Entity.Order = Order;
|
||||||
|
|
||||||
|
Entity.ProfileConfigurations.Clear();
|
||||||
|
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||||
|
{
|
||||||
|
profileConfiguration.Save();
|
||||||
|
Entity.ProfileConfigurations.Add(profileConfiguration.Entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a profile configuration is added to this <see cref="ProfileCategory" />
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ProfileConfigurationEventArgs>? ProfileConfigurationAdded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a profile configuration is removed from this <see cref="ProfileCategory" />
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ProfileConfigurationEventArgs>? ProfileConfigurationRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="ProfileConfigurationAdded" /> event
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnProfileConfigurationAdded(ProfileConfigurationEventArgs e)
|
||||||
|
{
|
||||||
|
ProfileConfigurationAdded?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="ProfileConfigurationRemoved" /> event
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnProfileConfigurationRemoved(ProfileConfigurationEventArgs e)
|
||||||
|
{
|
||||||
|
ProfileConfigurationRemoved?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a name of one of the default categories
|
||||||
|
/// </summary>
|
||||||
|
public enum DefaultCategoryName
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category used by profiles tied to games
|
||||||
|
/// </summary>
|
||||||
|
Games,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The category used by profiles tied to applications
|
||||||
|
/// </summary>
|
||||||
|
Applications,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The category used by general profiles
|
||||||
|
/// </summary>
|
||||||
|
General
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a type of behaviour when this profile is activated
|
||||||
|
/// </summary>
|
||||||
|
public enum ActivationBehaviour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Do nothing to other profiles
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable all other profiles
|
||||||
|
/// </summary>
|
||||||
|
DisableOthers,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable all other profiles below this one
|
||||||
|
/// </summary>
|
||||||
|
DisableOthersBelow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable all other profiles above this one
|
||||||
|
/// </summary>
|
||||||
|
DisableOthersAbove,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable all other profiles in the same category
|
||||||
|
/// </summary>
|
||||||
|
DisableOthersInCategory,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable all other profiles below this one in the same category
|
||||||
|
/// </summary>
|
||||||
|
DisableOthersBelowInCategory,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable all other profiles above this one in the same category
|
||||||
|
/// </summary>
|
||||||
|
DisableOthersAboveInCategory
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.Storage.Entities.Profile;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a descriptor that describes a profile
|
|
||||||
/// </summary>
|
|
||||||
public class ProfileDescriptor : CorePropertyChanged
|
|
||||||
{
|
|
||||||
internal ProfileDescriptor(ProfileModule profileModule, ProfileEntity profileEntity)
|
|
||||||
{
|
|
||||||
ProfileModule = profileModule;
|
|
||||||
|
|
||||||
Id = profileEntity.Id;
|
|
||||||
Name = profileEntity.Name;
|
|
||||||
IsLastActiveProfile = profileEntity.IsActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the module backing the profile
|
|
||||||
/// </summary>
|
|
||||||
public ProfileModule ProfileModule { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the unique ID of the profile by which it can be loaded from storage
|
|
||||||
/// </summary>
|
|
||||||
public Guid Id { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the profile
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean indicating whether this was the last active profile
|
|
||||||
/// </summary>
|
|
||||||
public bool IsLastActiveProfile { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -11,15 +11,14 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ProfileElement : CorePropertyChanged, IDisposable
|
public abstract class ProfileElement : CorePropertyChanged, IDisposable
|
||||||
{
|
{
|
||||||
private bool _suspended;
|
|
||||||
private Guid _entityId;
|
private Guid _entityId;
|
||||||
private string? _name;
|
private string? _name;
|
||||||
private int _order;
|
private int _order;
|
||||||
private ProfileElement? _parent;
|
private ProfileElement? _parent;
|
||||||
private Profile _profile;
|
private Profile _profile;
|
||||||
|
private bool _suspended;
|
||||||
|
|
||||||
internal List<ProfileElement> ChildrenList;
|
internal List<ProfileElement> ChildrenList;
|
||||||
internal bool Disposed;
|
|
||||||
|
|
||||||
internal ProfileElement(Profile profile)
|
internal ProfileElement(Profile profile)
|
||||||
{
|
{
|
||||||
@ -95,6 +94,11 @@ namespace Artemis.Core
|
|||||||
set => SetAndNotify(ref _suspended, value);
|
set => SetAndNotify(ref _suspended, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the profile element is disposed
|
||||||
|
/// </summary>
|
||||||
|
public bool Disposed { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the element
|
/// Updates the element
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -0,0 +1,210 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
public class ProfileConfiguration : CorePropertyChanged, IStorageModel
|
||||||
|
{
|
||||||
|
private ProfileCategory _category;
|
||||||
|
|
||||||
|
private bool _isMissingModule;
|
||||||
|
private bool _isSuspended;
|
||||||
|
private Module? _module;
|
||||||
|
private string _name;
|
||||||
|
private int _order;
|
||||||
|
private Profile? _profile;
|
||||||
|
|
||||||
|
internal ProfileConfiguration(ProfileCategory category, string name, string icon)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_category = category;
|
||||||
|
|
||||||
|
Entity = new ProfileConfigurationEntity();
|
||||||
|
Icon = new ProfileConfigurationIcon(Entity) {MaterialIcon = icon};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ProfileConfiguration(ProfileCategory category, ProfileConfigurationEntity entity)
|
||||||
|
{
|
||||||
|
// Will be loaded from the entity
|
||||||
|
_name = null!;
|
||||||
|
_category = category;
|
||||||
|
|
||||||
|
Entity = entity;
|
||||||
|
Icon = new ProfileConfigurationIcon(Entity);
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of this profile configuration
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => SetAndNotify(ref _name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The order in which this profile appears in the update loop and sidebar
|
||||||
|
/// </summary>
|
||||||
|
public int Order
|
||||||
|
{
|
||||||
|
get => _order;
|
||||||
|
set => SetAndNotify(ref _order, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether this profile is suspended, disabling it regardless of the
|
||||||
|
/// <see cref="ActivationCondition" />
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuspended
|
||||||
|
{
|
||||||
|
get => _isSuspended;
|
||||||
|
set => SetAndNotify(ref _isSuspended, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether this profile configuration is missing any modules
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMissingModule
|
||||||
|
{
|
||||||
|
get => _isMissingModule;
|
||||||
|
private set => SetAndNotify(ref _isMissingModule, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the category of this profile configuration
|
||||||
|
/// </summary>
|
||||||
|
public ProfileCategory Category
|
||||||
|
{
|
||||||
|
get => _category;
|
||||||
|
internal set => SetAndNotify(ref _category, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the icon configuration
|
||||||
|
/// </summary>
|
||||||
|
public ProfileConfigurationIcon Icon { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile of this profile configuration
|
||||||
|
/// </summary>
|
||||||
|
public Profile? Profile
|
||||||
|
{
|
||||||
|
get => _profile;
|
||||||
|
internal set => SetAndNotify(ref _profile, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the behaviour of when this profile is activated
|
||||||
|
/// </summary>
|
||||||
|
public ActivationBehaviour ActivationBehaviour { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data model condition that must evaluate to <see langword="true" /> for this profile to be activated
|
||||||
|
/// alongside any activation requirements of the <see cref="Module" />, if set
|
||||||
|
/// </summary>
|
||||||
|
public DataModelConditionGroup? ActivationCondition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the module this profile uses
|
||||||
|
/// </summary>
|
||||||
|
public Module? Module
|
||||||
|
{
|
||||||
|
get => _module;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_module = value;
|
||||||
|
IsMissingModule = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the activation conditions where met during the last <see cref="Update" /> call
|
||||||
|
/// </summary>
|
||||||
|
public bool ActivationConditionMet { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether this profile configuration is being edited
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBeingEdited { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity used by this profile config
|
||||||
|
/// </summary>
|
||||||
|
public ProfileConfigurationEntity Entity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates this configurations activation condition status
|
||||||
|
/// </summary>
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
ActivationConditionMet = ActivationCondition == null || ActivationCondition.Evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldBeActive(bool includeActivationCondition)
|
||||||
|
{
|
||||||
|
if (Category.IsSuspended || IsSuspended || IsMissingModule)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (includeActivationCondition)
|
||||||
|
return ActivationConditionMet && (Module == null || Module.IsActivated);
|
||||||
|
return Module == null || Module.IsActivated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[ProfileConfiguration] {nameof(Name)}: {Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadModules(List<Module> enabledModules)
|
||||||
|
{
|
||||||
|
Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId);
|
||||||
|
IsMissingModule = Module == null && Entity.ModuleId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IStorageModel
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
Name = Entity.Name;
|
||||||
|
IsSuspended = Entity.IsSuspended;
|
||||||
|
ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour;
|
||||||
|
|
||||||
|
Icon.Load();
|
||||||
|
|
||||||
|
ActivationCondition = Entity.ActivationCondition != null
|
||||||
|
? new DataModelConditionGroup(null, Entity.ActivationCondition)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
Entity.Name = Name;
|
||||||
|
Entity.IsSuspended = IsSuspended;
|
||||||
|
Entity.ActivationBehaviour = (int) ActivationBehaviour;
|
||||||
|
Entity.ProfileCategoryId = Category.Entity.Id;
|
||||||
|
|
||||||
|
Icon.Save();
|
||||||
|
|
||||||
|
if (ActivationCondition != null)
|
||||||
|
{
|
||||||
|
ActivationCondition.Save();
|
||||||
|
Entity.ActivationCondition = ActivationCondition.Entity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Entity.ActivationCondition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsMissingModule)
|
||||||
|
Entity.ModuleId = Module?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Artemis.Core.JsonConverters;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A model that can be used to serialize a profile configuration, it's profile and it's icon
|
||||||
|
/// </summary>
|
||||||
|
public class ProfileConfigurationExportModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the storage entity of the profile configuration
|
||||||
|
/// </summary>
|
||||||
|
public ProfileConfigurationEntity? ProfileConfigurationEntity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the storage entity of the profile
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(Required = Required.Always)]
|
||||||
|
public ProfileEntity ProfileEntity { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a stream containing the profile image
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(StreamConverter))]
|
||||||
|
public Stream? ProfileImage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the icon of a <see cref="ProfileConfiguration" />
|
||||||
|
/// </summary>
|
||||||
|
public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||||
|
{
|
||||||
|
private readonly ProfileConfigurationEntity _entity;
|
||||||
|
private Stream? _fileIcon;
|
||||||
|
private ProfileConfigurationIconType _iconType;
|
||||||
|
private string? _materialIcon;
|
||||||
|
|
||||||
|
internal ProfileConfigurationIcon(ProfileConfigurationEntity entity)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of icon this profile configuration uses
|
||||||
|
/// </summary>
|
||||||
|
public ProfileConfigurationIconType IconType
|
||||||
|
{
|
||||||
|
get => _iconType;
|
||||||
|
set => SetAndNotify(ref _iconType, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the icon if it is a Material icon
|
||||||
|
/// </summary>
|
||||||
|
public string? MaterialIcon
|
||||||
|
{
|
||||||
|
get => _materialIcon;
|
||||||
|
set => SetAndNotify(ref _materialIcon, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a stream containing the icon if it is bitmap or SVG
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Stream? FileIcon
|
||||||
|
{
|
||||||
|
get => _fileIcon;
|
||||||
|
set => SetAndNotify(ref _fileIcon, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IStorageModel
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
IconType = (ProfileConfigurationIconType) _entity.IconType;
|
||||||
|
MaterialIcon = _entity.MaterialIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_entity.IconType = (int) IconType;
|
||||||
|
_entity.MaterialIcon = MaterialIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a type of profile icon
|
||||||
|
/// </summary>
|
||||||
|
public enum ProfileConfigurationIconType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An icon picked from the Material Design Icons collection
|
||||||
|
/// </summary>
|
||||||
|
[Description("Material Design Icon")] MaterialIcon,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmap image icon
|
||||||
|
/// </summary>
|
||||||
|
[Description("Bitmap Image")] BitmapImage,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An SVG image icon
|
||||||
|
/// </summary>
|
||||||
|
[Description("SVG Image")] SvgImage
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,53 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Artemis.Core.DataModelExpansions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Allows you to expand the application-wide datamodel
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DataModelExpansion<T> : BaseDataModelExpansion where T : DataModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The main data model of this data model expansion
|
|
||||||
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
|
|
||||||
/// </summary>
|
|
||||||
public T DataModel
|
|
||||||
{
|
|
||||||
get => InternalDataModel as T ?? throw new InvalidOperationException("Internal datamodel does not match the type of the data model");
|
|
||||||
internal set => InternalDataModel = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
|
|
||||||
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
|
||||||
{
|
|
||||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
|
||||||
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
|
|
||||||
HiddenPropertiesList.Add(propertyInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
|
|
||||||
/// dm.TimeDataModel.CurrentTimeUTC)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
|
|
||||||
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
|
||||||
{
|
|
||||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
|
||||||
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void InternalEnable()
|
|
||||||
{
|
|
||||||
DataModel = Activator.CreateInstance<T>();
|
|
||||||
DataModel.Feature = this;
|
|
||||||
DataModel.DataModelDescription = GetDataModelDescription();
|
|
||||||
base.InternalEnable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Artemis.Core.DataModelExpansions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// For internal use only, to implement your own layer property type, extend <see cref="DataModelExpansion{T}" />
|
|
||||||
/// instead.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BaseDataModelExpansion : DataModelPluginFeature
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
|
||||||
/// </summary>
|
|
||||||
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
|
||||||
|
|
||||||
internal DataModel? InternalDataModel { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called each frame when the data model should update
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
|
||||||
public abstract void Update(double deltaTime);
|
|
||||||
|
|
||||||
internal void InternalUpdate(double deltaTime)
|
|
||||||
{
|
|
||||||
if (InternalDataModel != null)
|
|
||||||
Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
|
||||||
/// name and description
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
|
||||||
{
|
|
||||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an feature of a certain type provided by a plugin with support for data models
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DataModelPluginFeature : PluginFeature
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,7 +6,7 @@ using SkiaSharp;
|
|||||||
namespace Artemis.Core.LayerBrushes
|
namespace Artemis.Core.LayerBrushes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="RgbNetLayerBrush{T}" /> or instead
|
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable
|
public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Artemis.Core.LayerBrushes
|
namespace Artemis.Core.LayerBrushes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="RgbNetLayerBrush{T}" /> or instead
|
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PropertiesLayerBrush<T> : BaseLayerBrush where T : LayerPropertyGroup
|
public abstract class PropertiesLayerBrush<T> : BaseLayerBrush where T : LayerPropertyGroup
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,104 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Ninject;
|
|
||||||
using RGB.NET.Core;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core.LayerBrushes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An RGB.NET brush that uses RGB.NET's per-LED rendering engine.
|
|
||||||
/// <para>Note: This brush type always renders on top of regular brushes</para>
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
public abstract class RgbNetLayerBrush<T> : PropertiesLayerBrush<T> where T : LayerPropertyGroup
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="RgbNetLayerBrush{T}" /> class
|
|
||||||
/// </summary>
|
|
||||||
protected RgbNetLayerBrush()
|
|
||||||
{
|
|
||||||
BrushType = LayerBrushType.RgbNet;
|
|
||||||
SupportsTransformation = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The LED group this layer effect is applied to
|
|
||||||
/// </summary>
|
|
||||||
public ListLedGroup? LedGroup { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For internal use only, is public for dependency injection but ignore pl0x
|
|
||||||
/// </summary>
|
|
||||||
[Inject]
|
|
||||||
public IRgbService? RgbService { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when Artemis needs an instance of the RGB.NET effect you are implementing
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Your RGB.NET effect</returns>
|
|
||||||
public abstract IBrush GetBrush();
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
if (RgbService == null)
|
|
||||||
throw new ArtemisCoreException("Cannot dispose RGB.NET layer brush because RgbService is not set");
|
|
||||||
|
|
||||||
Layer.RenderPropertiesUpdated -= LayerOnRenderPropertiesUpdated;
|
|
||||||
LedGroup?.Detach();
|
|
||||||
LedGroup = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
internal void UpdateLedGroup()
|
|
||||||
{
|
|
||||||
if (LedGroup == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Layer.Parent != null)
|
|
||||||
LedGroup.ZIndex = Layer.Parent.Children.Count - Layer.Parent.Children.IndexOf(Layer);
|
|
||||||
else
|
|
||||||
LedGroup.ZIndex = 1;
|
|
||||||
|
|
||||||
List<Led> missingLeds = Layer.Leds.Where(l => !LedGroup.ContainsLed(l.RgbLed)).Select(l => l.RgbLed).ToList();
|
|
||||||
List<Led> extraLeds = LedGroup.Where(l => Layer.Leds.All(layerLed => layerLed.RgbLed != l)).ToList();
|
|
||||||
LedGroup.AddLeds(missingLeds);
|
|
||||||
LedGroup.RemoveLeds(extraLeds);
|
|
||||||
LedGroup.Brush = GetBrush();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Initialize()
|
|
||||||
{
|
|
||||||
if (RgbService == null)
|
|
||||||
throw new ArtemisCoreException("Cannot initialize RGB.NET layer brush because RgbService is not set");
|
|
||||||
|
|
||||||
LedGroup = new ListLedGroup(RgbService.Surface);
|
|
||||||
Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated;
|
|
||||||
|
|
||||||
InitializeProperties();
|
|
||||||
UpdateLedGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not used in this effect type
|
|
||||||
internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("RGB.NET layer effects do not implement InternalRender");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LayerOnRenderPropertiesUpdated(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
UpdateLedGroup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,6 +7,7 @@ using System.Reflection;
|
|||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Module = Artemis.Core.Modules.Module;
|
||||||
|
|
||||||
namespace Artemis.Core.DataModelExpansions
|
namespace Artemis.Core.DataModelExpansions
|
||||||
{
|
{
|
||||||
@ -23,16 +24,16 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
protected DataModel()
|
protected DataModel()
|
||||||
{
|
{
|
||||||
// These are both set right after construction to keep the constructor of inherited classes clean
|
// These are both set right after construction to keep the constructor of inherited classes clean
|
||||||
Feature = null!;
|
Module = null!;
|
||||||
DataModelDescription = null!;
|
DataModelDescription = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin feature this data model belongs to
|
/// Gets the module this data model belongs to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
[DataModelIgnore]
|
[DataModelIgnore]
|
||||||
public DataModelPluginFeature Feature { get; internal set; }
|
public Module Module { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
|
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
|
||||||
@ -59,10 +60,8 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
|
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
|
||||||
{
|
{
|
||||||
if (Feature is ProfileModule profileModule)
|
if (Module is Module module)
|
||||||
return profileModule.HiddenProperties;
|
return module.HiddenProperties;
|
||||||
if (Feature is BaseDataModelExpansion dataModelExpansion)
|
|
||||||
return dataModelExpansion.HiddenProperties;
|
|
||||||
|
|
||||||
return new List<PropertyInfo>().AsReadOnly();
|
return new List<PropertyInfo>().AsReadOnly();
|
||||||
}
|
}
|
||||||
@ -149,7 +148,7 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
attribute.Name ??= key.Humanize();
|
attribute.Name ??= key.Humanize();
|
||||||
if (initialValue is DataModel dynamicDataModel)
|
if (initialValue is DataModel dynamicDataModel)
|
||||||
{
|
{
|
||||||
dynamicDataModel.Feature = Feature;
|
dynamicDataModel.Module = Module;
|
||||||
dynamicDataModel.DataModelDescription = attribute;
|
dynamicDataModel.DataModelDescription = attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,21 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
using Artemis.Core.DataModelExpansions;
|
using Artemis.Core.DataModelExpansions;
|
||||||
using Artemis.Storage.Entities.Module;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Modules
|
namespace Artemis.Core.Modules
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows you to add support for new games/applications while utilizing your own data model
|
/// Allows you to add new data to the Artemis data model
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Module<T> : Module where T : DataModel
|
public abstract class Module<T> : Module where T : DataModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The data model driving this module
|
/// The data model driving this module
|
||||||
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
|
/// <para>Note: This default data model is automatically registered and instantiated upon plugin enable</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T DataModel
|
public T DataModel
|
||||||
{
|
{
|
||||||
@ -24,43 +25,61 @@ namespace Artemis.Core.Modules
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether this module must also expand the main data model
|
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
|
||||||
/// <para>
|
|
||||||
/// Note: If expanding the main data model is all you want your plugin to do, create a
|
|
||||||
/// <see cref="DataModelExpansion{T}" /> plugin instead.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ExpandsDataModel
|
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
|
||||||
|
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||||
{
|
{
|
||||||
get => InternalExpandsMainDataModel;
|
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||||
set => InternalExpandsMainDataModel = value;
|
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
|
||||||
|
HiddenPropertiesList.Add(propertyInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
|
||||||
/// name and description
|
/// dm.TimeDataModel.CurrentTimeUTC)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
|
||||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
||||||
{
|
{
|
||||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
||||||
|
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void InternalEnable()
|
internal override void InternalEnable()
|
||||||
{
|
{
|
||||||
DataModel = Activator.CreateInstance<T>();
|
DataModel = Activator.CreateInstance<T>();
|
||||||
DataModel.Feature = this;
|
DataModel.Module = this;
|
||||||
DataModel.DataModelDescription = GetDataModelDescription();
|
DataModel.DataModelDescription = GetDataModelDescription();
|
||||||
base.InternalEnable();
|
base.InternalEnable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void InternalDisable()
|
||||||
|
{
|
||||||
|
Deactivate(true);
|
||||||
|
base.InternalDisable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows you to add support for new games/applications
|
/// For internal use only, please use <see cref="Module{T}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Module : DataModelPluginFeature
|
public abstract class Module : PluginFeature
|
||||||
{
|
{
|
||||||
|
private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new();
|
||||||
|
private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||||
|
/// </summary>
|
||||||
|
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read only collection of default profile paths
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The modules display name that's shown in the menu
|
/// The modules display name that's shown in the menu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -107,35 +126,23 @@ namespace Artemis.Core.Modules
|
|||||||
public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any;
|
public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the default priority category for this module, defaults to
|
/// Gets or sets a boolean indicating whether this module is always available to profiles or only when profiles
|
||||||
/// <see cref="ModulePriorityCategory.Normal" />
|
/// specifically target this module.
|
||||||
|
/// <para>Note: If set to <see langword="true" />, <see cref="ActivationRequirements" /> are not evaluated.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ModulePriorityCategory DefaultPriorityCategory { get; set; } = ModulePriorityCategory.Normal;
|
public bool IsAlwaysAvailable { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current priority category of this module
|
|
||||||
/// </summary>
|
|
||||||
public ModulePriorityCategory PriorityCategory { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current priority of this module within its priority category
|
|
||||||
/// </summary>
|
|
||||||
public int Priority { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of custom module tabs that show in the UI
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ModuleTab>? ModuleTabs { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether updating this module is currently allowed
|
/// Gets whether updating this module is currently allowed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
|
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
|
||||||
|
|
||||||
internal DataModel? InternalDataModel { get; set; }
|
/// <summary>
|
||||||
|
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
||||||
|
|
||||||
internal bool InternalExpandsMainDataModel { get; set; }
|
internal DataModel? InternalDataModel { get; set; }
|
||||||
internal ModuleSettingsEntity? SettingsEntity { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called each frame when the module should update
|
/// Called each frame when the module should update
|
||||||
@ -143,14 +150,6 @@ namespace Artemis.Core.Modules
|
|||||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
/// <param name="deltaTime">Time in seconds since the last update</param>
|
||||||
public abstract void Update(double deltaTime);
|
public abstract void Update(double deltaTime);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called each frame when the module should render
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="deltaTime">Time since the last render</param>
|
|
||||||
/// <param name="canvas"></param>
|
|
||||||
/// <param name="canvasInfo"></param>
|
|
||||||
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the <see cref="ActivationRequirements" /> are met or during an override
|
/// Called when the <see cref="ActivationRequirements" /> are met or during an override
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -158,7 +157,9 @@ namespace Artemis.Core.Modules
|
|||||||
/// If true, the activation was due to an override. This usually means the module was activated
|
/// If true, the activation was due to an override. This usually means the module was activated
|
||||||
/// by the profile editor
|
/// by the profile editor
|
||||||
/// </param>
|
/// </param>
|
||||||
public abstract void ModuleActivated(bool isOverride);
|
public virtual void ModuleActivated(bool isOverride)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the <see cref="ActivationRequirements" /> are no longer met or during an override
|
/// Called when the <see cref="ActivationRequirements" /> are no longer met or during an override
|
||||||
@ -167,7 +168,9 @@ namespace Artemis.Core.Modules
|
|||||||
/// If true, the deactivation was due to an override. This usually means the module was deactivated
|
/// If true, the deactivation was due to an override. This usually means the module was deactivated
|
||||||
/// by the profile editor
|
/// by the profile editor
|
||||||
/// </param>
|
/// </param>
|
||||||
public abstract void ModuleDeactivated(bool isOverride);
|
public virtual void ModuleDeactivated(bool isOverride)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the activation requirements following the <see cref="ActivationRequirementMode" /> and returns the result
|
/// Evaluates the activation requirements following the <see cref="ActivationRequirementMode" /> and returns the result
|
||||||
@ -185,6 +188,50 @@ namespace Artemis.Core.Modules
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
||||||
|
/// name and description
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
||||||
|
{
|
||||||
|
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a default profile by reading it from the file found at the provided path
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category">The category in which to place the default profile</param>
|
||||||
|
/// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
|
||||||
|
/// already in the list.
|
||||||
|
/// </returns>
|
||||||
|
protected bool AddDefaultProfile(DefaultCategoryName category, string file)
|
||||||
|
{
|
||||||
|
// It can be null if the plugin has not loaded yet in which case Plugin.ResolveRelativePath fails
|
||||||
|
if (Plugin == null!)
|
||||||
|
{
|
||||||
|
if (_pendingDefaultProfilePaths.Contains((category, file)))
|
||||||
|
return false;
|
||||||
|
_pendingDefaultProfilePaths.Add((category, file));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Path.IsPathRooted(file))
|
||||||
|
file = Plugin.ResolveRelativePath(file);
|
||||||
|
|
||||||
|
// Ensure the file exists
|
||||||
|
if (!File.Exists(file))
|
||||||
|
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
|
||||||
|
|
||||||
|
if (_defaultProfilePaths.Contains((category, file)))
|
||||||
|
return false;
|
||||||
|
_defaultProfilePaths.Add((category, file));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
internal virtual void InternalUpdate(double deltaTime)
|
internal virtual void InternalUpdate(double deltaTime)
|
||||||
{
|
{
|
||||||
StartUpdateMeasure();
|
StartUpdateMeasure();
|
||||||
@ -193,13 +240,6 @@ namespace Artemis.Core.Modules
|
|||||||
StopUpdateMeasure();
|
StopUpdateMeasure();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
|
||||||
{
|
|
||||||
StartRenderMeasure();
|
|
||||||
Render(deltaTime, canvas, canvasInfo);
|
|
||||||
StopRenderMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual void Activate(bool isOverride)
|
internal virtual void Activate(bool isOverride)
|
||||||
{
|
{
|
||||||
if (IsActivated)
|
if (IsActivated)
|
||||||
@ -220,6 +260,20 @@ namespace Artemis.Core.Modules
|
|||||||
ModuleDeactivated(isOverride);
|
ModuleDeactivated(isOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Overrides of PluginFeature
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
internal override void InternalEnable()
|
||||||
|
{
|
||||||
|
foreach ((DefaultCategoryName categoryName, var path) in _pendingDefaultProfilePaths)
|
||||||
|
AddDefaultProfile(categoryName, path);
|
||||||
|
_pendingDefaultProfilePaths.Clear();
|
||||||
|
|
||||||
|
base.InternalEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
internal virtual void Reactivate(bool isDeactivateOverride, bool isActivateOverride)
|
internal virtual void Reactivate(bool isDeactivateOverride, bool isActivateOverride)
|
||||||
{
|
{
|
||||||
if (!IsActivated)
|
if (!IsActivated)
|
||||||
@ -228,16 +282,6 @@ namespace Artemis.Core.Modules
|
|||||||
Deactivate(isDeactivateOverride);
|
Deactivate(isDeactivateOverride);
|
||||||
Activate(isActivateOverride);
|
Activate(isActivateOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ApplyToEntity()
|
|
||||||
{
|
|
||||||
if (SettingsEntity == null)
|
|
||||||
SettingsEntity = new ModuleSettingsEntity();
|
|
||||||
|
|
||||||
SettingsEntity.ModuleId = Id;
|
|
||||||
SettingsEntity.PriorityCategory = (int) PriorityCategory;
|
|
||||||
SettingsEntity.Priority = Priority;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -255,25 +299,4 @@ namespace Artemis.Core.Modules
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
All
|
All
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes the priority category of a module
|
|
||||||
/// </summary>
|
|
||||||
public enum ModulePriorityCategory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a normal render priority
|
|
||||||
/// </summary>
|
|
||||||
Normal,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the module renders for a specific application/game, rendering on top of normal modules
|
|
||||||
/// </summary>
|
|
||||||
Application,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the module renders an overlay, always rendering on top
|
|
||||||
/// </summary>
|
|
||||||
Overlay
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Modules
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class ModuleTab<T> : ModuleTab where T : IModuleViewModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ModuleTab{T}" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">The title of the tab</param>
|
|
||||||
public ModuleTab(string title) : base(title)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type Type => typeof(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes a UI tab for a specific module
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ModuleTab
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ModuleTab" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">The title of the tab</param>
|
|
||||||
protected ModuleTab(string title)
|
|
||||||
{
|
|
||||||
Title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The title of the tab
|
|
||||||
/// </summary>
|
|
||||||
public string Title { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of view model the tab contains
|
|
||||||
/// </summary>
|
|
||||||
public abstract Type Type { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,329 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.Core.DataModelExpansions;
|
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.Storage.Entities.Profile;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Modules
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Allows you to add support for new games/applications while utilizing Artemis' profile engine and your own data
|
|
||||||
/// model
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ProfileModule<T> : ProfileModule where T : DataModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The data model driving this module
|
|
||||||
/// <para>Note: This default data model is automatically registered upon plugin enable</para>
|
|
||||||
/// </summary>
|
|
||||||
public T DataModel
|
|
||||||
{
|
|
||||||
get => InternalDataModel as T ?? throw new InvalidOperationException("Internal datamodel does not match the type of the data model");
|
|
||||||
internal set => InternalDataModel = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether this module must also expand the main data model
|
|
||||||
/// <para>
|
|
||||||
/// Note: If expanding the main data model is all you want your plugin to do, create a
|
|
||||||
/// <see cref="BaseDataModelExpansion" /> plugin instead.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public bool ExpandsDataModel
|
|
||||||
{
|
|
||||||
get => InternalExpandsMainDataModel;
|
|
||||||
set => InternalExpandsMainDataModel = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
|
||||||
/// name and description
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public virtual DataModelPropertyAttribute GetDataModelDescription()
|
|
||||||
{
|
|
||||||
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
|
|
||||||
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
|
||||||
{
|
|
||||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
|
||||||
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
|
|
||||||
HiddenPropertiesList.Add(propertyInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
|
|
||||||
/// dm.TimeDataModel.CurrentTimeUTC)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
|
|
||||||
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
|
|
||||||
{
|
|
||||||
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
|
|
||||||
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void InternalEnable()
|
|
||||||
{
|
|
||||||
DataModel = Activator.CreateInstance<T>();
|
|
||||||
DataModel.Feature = this;
|
|
||||||
DataModel.DataModelDescription = GetDataModelDescription();
|
|
||||||
base.InternalEnable();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void InternalDisable()
|
|
||||||
{
|
|
||||||
Deactivate(true);
|
|
||||||
base.InternalDisable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Allows you to add support for new games/applications while utilizing Artemis' profile engine
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ProfileModule : Module
|
|
||||||
{
|
|
||||||
private readonly List<string> _defaultProfilePaths = new();
|
|
||||||
private readonly List<string> _pendingDefaultProfilePaths = new();
|
|
||||||
private readonly List<ProfileEntity> _defaultProfiles = new();
|
|
||||||
private readonly object _lock = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
|
||||||
/// </summary>
|
|
||||||
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ProfileModule" /> class
|
|
||||||
/// </summary>
|
|
||||||
protected ProfileModule()
|
|
||||||
{
|
|
||||||
OpacityOverride = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the currently active profile
|
|
||||||
/// </summary>
|
|
||||||
public Profile? ActiveProfile { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disables updating the profile, rendering does continue
|
|
||||||
/// </summary>
|
|
||||||
public bool IsProfileUpdatingDisabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the opacity of the root folder
|
|
||||||
/// </summary>
|
|
||||||
public double OpacityOverride { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether or not a profile change is being animated
|
|
||||||
/// </summary>
|
|
||||||
public bool AnimatingProfileChange { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of default profiles, to add a new default profile use <see cref="AddDefaultProfile" />
|
|
||||||
/// </summary>
|
|
||||||
internal ReadOnlyCollection<ProfileEntity> DefaultProfiles => _defaultProfiles.AsReadOnly();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called after the profile has updated
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
|
||||||
public virtual void ProfileUpdated(double deltaTime)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called after the profile has rendered
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="deltaTime">Time since the last render</param>
|
|
||||||
/// <param name="canvas"></param>
|
|
||||||
/// <param name="canvasInfo"></param>
|
|
||||||
public virtual void ProfileRendered(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the <see cref="ActiveProfile" /> has changed
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? ActiveProfileChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a default profile by reading it from the file found at the provided path
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
|
|
||||||
/// already in the list.
|
|
||||||
/// </returns>
|
|
||||||
protected bool AddDefaultProfile(string file)
|
|
||||||
{
|
|
||||||
// It can be null if the plugin has not loaded yet...
|
|
||||||
if (Plugin == null!)
|
|
||||||
{
|
|
||||||
if (_pendingDefaultProfilePaths.Contains(file))
|
|
||||||
return false;
|
|
||||||
_pendingDefaultProfilePaths.Add(file);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Path.IsPathRooted(file))
|
|
||||||
file = Plugin.ResolveRelativePath(file);
|
|
||||||
|
|
||||||
if (_defaultProfilePaths.Contains(file))
|
|
||||||
return false;
|
|
||||||
_defaultProfilePaths.Add(file);
|
|
||||||
|
|
||||||
// Ensure the file exists
|
|
||||||
if (!File.Exists(file))
|
|
||||||
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
|
|
||||||
// Deserialize and make sure that succeeded
|
|
||||||
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(File.ReadAllText(file), ProfileService.ExportSettings);
|
|
||||||
if (profileEntity == null)
|
|
||||||
throw new ArtemisPluginFeatureException(this, $"Failed to deserialize default profile at {file}.");
|
|
||||||
// Ensure the profile ID is unique
|
|
||||||
if (_defaultProfiles.Any(d => d.Id == profileEntity.Id))
|
|
||||||
throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {profileEntity.Id} already in use.");
|
|
||||||
|
|
||||||
profileEntity.IsFreshImport = true;
|
|
||||||
profileEntity.IsActive = false;
|
|
||||||
_defaultProfiles.Add(profileEntity);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="ActiveProfileChanged" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnActiveProfileChanged()
|
|
||||||
{
|
|
||||||
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void InternalEnable()
|
|
||||||
{
|
|
||||||
foreach (string pendingDefaultProfile in _pendingDefaultProfilePaths)
|
|
||||||
AddDefaultProfile(pendingDefaultProfile);
|
|
||||||
_pendingDefaultProfilePaths.Clear();
|
|
||||||
|
|
||||||
base.InternalEnable();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void InternalUpdate(double deltaTime)
|
|
||||||
{
|
|
||||||
StartUpdateMeasure();
|
|
||||||
if (IsUpdateAllowed)
|
|
||||||
Update(deltaTime);
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
OpacityOverride = AnimatingProfileChange
|
|
||||||
? Math.Max(0, OpacityOverride - 0.1)
|
|
||||||
: Math.Min(1, OpacityOverride + 0.1);
|
|
||||||
|
|
||||||
// Update the profile
|
|
||||||
if (!IsProfileUpdatingDisabled)
|
|
||||||
ActiveProfile?.Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileUpdated(deltaTime);
|
|
||||||
StopUpdateMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
|
||||||
{
|
|
||||||
StartRenderMeasure();
|
|
||||||
Render(deltaTime, canvas, canvasInfo);
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
// Render the profile
|
|
||||||
ActiveProfile?.Render(canvas, SKPointI.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileRendered(deltaTime, canvas, canvasInfo);
|
|
||||||
StopRenderMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task ChangeActiveProfileAnimated(Profile? profile, IEnumerable<ArtemisDevice> devices)
|
|
||||||
{
|
|
||||||
if (profile != null && profile.Module != this)
|
|
||||||
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}.");
|
|
||||||
if (!IsActivated)
|
|
||||||
throw new ArtemisCoreException("Cannot activate a profile on a deactivated module");
|
|
||||||
|
|
||||||
if (profile == ActiveProfile || AnimatingProfileChange)
|
|
||||||
return;
|
|
||||||
|
|
||||||
AnimatingProfileChange = true;
|
|
||||||
|
|
||||||
while (OpacityOverride > 0)
|
|
||||||
await Task.Delay(50);
|
|
||||||
|
|
||||||
ChangeActiveProfile(profile, devices);
|
|
||||||
AnimatingProfileChange = false;
|
|
||||||
|
|
||||||
while (OpacityOverride < 1)
|
|
||||||
await Task.Delay(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ChangeActiveProfile(Profile? profile, IEnumerable<ArtemisDevice> devices)
|
|
||||||
{
|
|
||||||
if (profile != null && profile.Module != this)
|
|
||||||
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}.");
|
|
||||||
if (!IsActivated)
|
|
||||||
throw new ArtemisCoreException("Cannot activate a profile on a deactivated module");
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (profile == ActiveProfile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ActiveProfile?.Dispose();
|
|
||||||
|
|
||||||
ActiveProfile = profile;
|
|
||||||
ActiveProfile?.Activate(devices);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnActiveProfileChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Deactivate(bool isOverride)
|
|
||||||
{
|
|
||||||
base.Deactivate(isOverride);
|
|
||||||
|
|
||||||
Profile? profile = ActiveProfile;
|
|
||||||
ActiveProfile = null;
|
|
||||||
profile?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Reactivate(bool isDeactivateOverride, bool isActivateOverride)
|
|
||||||
{
|
|
||||||
if (!IsActivated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Avoid disposing the profile
|
|
||||||
base.Deactivate(isDeactivateOverride);
|
|
||||||
Activate(isActivateOverride);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -35,14 +35,10 @@ namespace Artemis.Core
|
|||||||
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
||||||
|
|
||||||
if (Icon != null) return;
|
if (Icon != null) return;
|
||||||
if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType))
|
if (typeof(DeviceProvider).IsAssignableFrom(featureType))
|
||||||
Icon = "TableAdd";
|
|
||||||
else if (typeof(DeviceProvider).IsAssignableFrom(featureType))
|
|
||||||
Icon = "Devices";
|
Icon = "Devices";
|
||||||
else if (typeof(ProfileModule).IsAssignableFrom(featureType))
|
|
||||||
Icon = "VectorRectangle";
|
|
||||||
else if (typeof(Module).IsAssignableFrom(featureType))
|
else if (typeof(Module).IsAssignableFrom(featureType))
|
||||||
Icon = "GearBox";
|
Icon = "VectorRectangle";
|
||||||
else if (typeof(LayerBrushProvider).IsAssignableFrom(featureType))
|
else if (typeof(LayerBrushProvider).IsAssignableFrom(featureType))
|
||||||
Icon = "Brush";
|
Icon = "Brush";
|
||||||
else if (typeof(LayerEffectProvider).IsAssignableFrom(featureType))
|
else if (typeof(LayerEffectProvider).IsAssignableFrom(featureType))
|
||||||
@ -66,10 +62,8 @@ namespace Artemis.Core
|
|||||||
if (Icon != null) return;
|
if (Icon != null) return;
|
||||||
Icon = Instance switch
|
Icon = Instance switch
|
||||||
{
|
{
|
||||||
BaseDataModelExpansion => "TableAdd",
|
|
||||||
DeviceProvider => "Devices",
|
DeviceProvider => "Devices",
|
||||||
ProfileModule => "VectorRectangle",
|
Module => "VectorRectangle",
|
||||||
Module => "GearBox",
|
|
||||||
LayerBrushProvider => "Brush",
|
LayerBrushProvider => "Brush",
|
||||||
LayerEffectProvider => "AutoAwesome",
|
LayerEffectProvider => "AutoAwesome",
|
||||||
_ => "Plugin"
|
_ => "Plugin"
|
||||||
|
|||||||
@ -29,11 +29,10 @@ namespace Artemis.Core.Services
|
|||||||
private readonly PluginSetting<LogEventLevel> _loggingLevel;
|
private readonly PluginSetting<LogEventLevel> _loggingLevel;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
|
private readonly IModuleService _moduleService;
|
||||||
private readonly IRgbService _rgbService;
|
private readonly IRgbService _rgbService;
|
||||||
private readonly List<Exception> _updateExceptions = new();
|
private readonly List<Exception> _updateExceptions = new();
|
||||||
private List<BaseDataModelExpansion> _dataModelExpansions = new();
|
|
||||||
private DateTime _lastExceptionLog;
|
private DateTime _lastExceptionLog;
|
||||||
private List<Module> _modules = new();
|
|
||||||
|
|
||||||
// ReSharper disable UnusedParameter.Local
|
// ReSharper disable UnusedParameter.Local
|
||||||
public CoreService(IKernel kernel,
|
public CoreService(IKernel kernel,
|
||||||
@ -43,8 +42,7 @@ namespace Artemis.Core.Services
|
|||||||
IPluginManagementService pluginManagementService,
|
IPluginManagementService pluginManagementService,
|
||||||
IRgbService rgbService,
|
IRgbService rgbService,
|
||||||
IProfileService profileService,
|
IProfileService profileService,
|
||||||
IModuleService moduleService // injected to ensure module priorities get applied
|
IModuleService moduleService)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Kernel = kernel;
|
Kernel = kernel;
|
||||||
Constants.CorePlugin.Kernel = kernel;
|
Constants.CorePlugin.Kernel = kernel;
|
||||||
@ -53,18 +51,14 @@ namespace Artemis.Core.Services
|
|||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_rgbService = rgbService;
|
_rgbService = rgbService;
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
|
_moduleService = moduleService;
|
||||||
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
|
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
|
||||||
_frameStopWatch = new Stopwatch();
|
_frameStopWatch = new Stopwatch();
|
||||||
StartupArguments = new List<string>();
|
StartupArguments = new List<string>();
|
||||||
|
|
||||||
UpdatePluginCache();
|
|
||||||
|
|
||||||
_rgbService.IsRenderPaused = true;
|
_rgbService.IsRenderPaused = true;
|
||||||
_rgbService.Surface.Updating += SurfaceOnUpdating;
|
_rgbService.Surface.Updating += SurfaceOnUpdating;
|
||||||
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
|
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
|
||||||
|
|
||||||
_pluginManagementService.PluginFeatureEnabled += (sender, args) => UpdatePluginCache();
|
|
||||||
_pluginManagementService.PluginFeatureDisabled += (sender, args) => UpdatePluginCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper restore UnusedParameter.Local
|
// ReSharper restore UnusedParameter.Local
|
||||||
@ -79,12 +73,6 @@ namespace Artemis.Core.Services
|
|||||||
FrameRendered?.Invoke(this, e);
|
FrameRendered?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePluginCache()
|
|
||||||
{
|
|
||||||
_modules = _pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).ToList();
|
|
||||||
_dataModelExpansions = _pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyLoggingLevel()
|
private void ApplyLoggingLevel()
|
||||||
{
|
{
|
||||||
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
|
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
|
||||||
@ -120,67 +108,18 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
_frameStopWatch.Restart();
|
_frameStopWatch.Restart();
|
||||||
|
|
||||||
// Render all active modules
|
_moduleService.UpdateActiveModules(args.DeltaTime);
|
||||||
SKTexture texture = _rgbService.OpenRender();
|
SKTexture texture = _rgbService.OpenRender();
|
||||||
|
|
||||||
lock (_dataModelExpansions)
|
|
||||||
{
|
|
||||||
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
|
|
||||||
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dataModelExpansion.InternalUpdate(args.DeltaTime);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_updateExceptions.Add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Module> modules;
|
|
||||||
lock (_modules)
|
|
||||||
{
|
|
||||||
modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel)
|
|
||||||
.OrderBy(m => m.PriorityCategory)
|
|
||||||
.ThenByDescending(m => m.Priority)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update all active modules
|
|
||||||
foreach (Module module in modules)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
module.InternalUpdate(args.DeltaTime);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_updateExceptions.Add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SKCanvas canvas = texture.Surface.Canvas;
|
SKCanvas canvas = texture.Surface.Canvas;
|
||||||
canvas.Save();
|
canvas.Save();
|
||||||
if (Math.Abs(texture.RenderScale - 1) > 0.001)
|
if (Math.Abs(texture.RenderScale - 1) > 0.001)
|
||||||
canvas.Scale(texture.RenderScale);
|
canvas.Scale(texture.RenderScale);
|
||||||
canvas.Clear(new SKColor(0, 0, 0));
|
canvas.Clear(new SKColor(0, 0, 0));
|
||||||
|
|
||||||
// While non-activated modules may be updated above if they expand the main data model, they may never render
|
if (!ProfileRenderingDisabled)
|
||||||
if (!ModuleRenderingDisabled)
|
|
||||||
{
|
{
|
||||||
foreach (Module module in modules.Where(m => m.IsActivated))
|
_profileService.UpdateProfiles(args.DeltaTime);
|
||||||
{
|
_profileService.RenderProfiles(canvas);
|
||||||
try
|
|
||||||
{
|
|
||||||
module.InternalRender(args.DeltaTime, canvas, texture.ImageInfo);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_updateExceptions.Add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
|
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
|
||||||
@ -228,7 +167,7 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan FrameTime { get; private set; }
|
public TimeSpan FrameTime { get; private set; }
|
||||||
public bool ModuleRenderingDisabled { get; set; }
|
public bool ProfileRenderingDisabled { get; set; }
|
||||||
public List<string> StartupArguments { get; set; }
|
public List<string> StartupArguments { get; set; }
|
||||||
public bool IsElevated { get; set; }
|
public bool IsElevated { get; set; }
|
||||||
|
|
||||||
@ -272,25 +211,6 @@ namespace Artemis.Core.Services
|
|||||||
OnInitialized();
|
OnInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayIntroAnimation()
|
|
||||||
{
|
|
||||||
IntroAnimation intro = new(_logger, _profileService, _rgbService.EnabledDevices);
|
|
||||||
|
|
||||||
// Draw a white overlay over the device
|
|
||||||
void DrawOverlay(object? sender, FrameRenderingEventArgs args)
|
|
||||||
{
|
|
||||||
if (intro.AnimationProfile.GetAllLayers().All(l => l.Timeline.IsFinished))
|
|
||||||
{
|
|
||||||
FrameRendering -= DrawOverlay;
|
|
||||||
intro.AnimationProfile.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
intro.Render(args.DeltaTime, args.Canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameRendering += DrawOverlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler? Initialized;
|
public event EventHandler? Initialized;
|
||||||
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
|
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
|
||||||
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
|
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
|
||||||
|
|||||||
@ -19,9 +19,9 @@ namespace Artemis.Core.Services
|
|||||||
TimeSpan FrameTime { get; }
|
TimeSpan FrameTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether modules are rendered each frame by calling their Render method
|
/// Gets or sets whether profiles are rendered each frame by calling their Render method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ModuleRenderingDisabled { get; set; }
|
bool ProfileRenderingDisabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of startup arguments
|
/// Gets or sets a list of startup arguments
|
||||||
@ -38,11 +38,6 @@ namespace Artemis.Core.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Plays the into animation profile defined in <c>Resources/intro-profile.json</c>
|
|
||||||
/// </summary>
|
|
||||||
void PlayIntroAnimation();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs the core has finished initializing
|
/// Occurs the core has finished initializing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Collections.Generic;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
|
|
||||||
namespace Artemis.Core.Services
|
namespace Artemis.Core.Services
|
||||||
@ -10,33 +10,29 @@ namespace Artemis.Core.Services
|
|||||||
public interface IModuleService : IArtemisService
|
public interface IModuleService : IArtemisService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current active module override. If set, all other modules are deactivated and only the
|
/// Updates all currently active modules
|
||||||
/// <see cref="ActiveModuleOverride" /> is active.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Module? ActiveModuleOverride { get; }
|
/// <param name="deltaTime"></param>
|
||||||
|
void UpdateActiveModules(double deltaTime);
|
||||||
/// <summary>
|
|
||||||
/// Changes the current <see cref="ActiveModuleOverride" /> and deactivates all other modules
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="overrideModule"></param>
|
|
||||||
Task SetActiveModuleOverride(Module? overrideModule);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates every enabled module's activation requirements and activates/deactivates modules accordingly
|
/// Evaluates every enabled module's activation requirements and activates/deactivates modules accordingly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task UpdateModuleActivation();
|
void UpdateModuleActivation();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the priority and priority category of the given module
|
/// Overrides activation on the provided module and restores regular activation to any remaining modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="module">The module to update</param>
|
void SetActivationOverride(Module? module);
|
||||||
/// <param name="category">The new priority category of the module</param>
|
|
||||||
/// <param name="priority">The new priority of the module</param>
|
|
||||||
void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the priority of a module is updated.
|
/// Occurs whenever a module is activated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler? ModulePriorityUpdated;
|
event EventHandler<ModuleEventArgs> ModuleActivated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs whenever a module is deactivated
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<ModuleEventArgs> ModuleDeactivated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,103 +1,48 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Newtonsoft.Json;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Timer = System.Timers.Timer;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Services
|
namespace Artemis.Core.Services
|
||||||
{
|
{
|
||||||
internal class ModuleService : IModuleService
|
internal class ModuleService : IModuleService
|
||||||
{
|
{
|
||||||
private static readonly SemaphoreSlim ActiveModuleSemaphore = new(1, 1);
|
private readonly Timer _activationUpdateTimer;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IModuleRepository _moduleRepository;
|
|
||||||
private readonly IProfileRepository _profileRepository;
|
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
|
private readonly List<Module> _modules;
|
||||||
|
private readonly object _updateLock = new();
|
||||||
|
|
||||||
public ModuleService(ILogger logger, IModuleRepository moduleRepository, IProfileRepository profileRepository, IPluginManagementService pluginManagementService, IProfileService profileService)
|
private Module? _activationOverride;
|
||||||
|
|
||||||
|
public ModuleService(ILogger logger, IPluginManagementService pluginManagementService, IProfileService profileService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_moduleRepository = moduleRepository;
|
|
||||||
_profileRepository = profileRepository;
|
|
||||||
_pluginManagementService = pluginManagementService;
|
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
|
|
||||||
|
|
||||||
Timer activationUpdateTimer = new(2000);
|
_activationUpdateTimer = new Timer(2000);
|
||||||
activationUpdateTimer.Start();
|
_activationUpdateTimer.Start();
|
||||||
activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
|
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
|
||||||
|
|
||||||
foreach (Module module in _pluginManagementService.GetFeaturesOfType<Module>())
|
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
|
||||||
InitialiseOrApplyPriority(module);
|
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
|
||||||
|
_modules = pluginManagementService.GetFeaturesOfType<Module>().ToList();
|
||||||
|
foreach (Module module in _modules)
|
||||||
|
ImportDefaultProfiles(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
|
protected virtual void OnModuleActivated(ModuleEventArgs e)
|
||||||
{
|
{
|
||||||
await UpdateModuleActivation();
|
ModuleActivated?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ActivateModule(Module module)
|
protected virtual void OnModuleDeactivated(ModuleEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
ModuleDeactivated?.Invoke(this, e);
|
||||||
{
|
|
||||||
ProfileModule? profileModule = module as ProfileModule;
|
|
||||||
|
|
||||||
if (profileModule != null && profileModule.DefaultProfiles.Any())
|
|
||||||
{
|
|
||||||
List<ProfileDescriptor> descriptors = _profileService.GetProfileDescriptors(profileModule);
|
|
||||||
foreach (ProfileEntity defaultProfile in profileModule.DefaultProfiles)
|
|
||||||
{
|
|
||||||
if (descriptors.All(d => d.Id != defaultProfile.Id))
|
|
||||||
_profileRepository.Add(defaultProfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.Activate(false);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// If this is a profile module, activate the last active profile after module activation
|
|
||||||
if (profileModule != null)
|
|
||||||
await _profileService.ActivateLastProfileAnimated(profileModule);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Warning(e, $"Failed to activate last profile on module {module}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeactivateModule(Module module)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// If this is a profile module, animate profile disable
|
|
||||||
// module.Deactivate would do the same but without animation
|
|
||||||
if (module.IsActivated && module is ProfileModule profileModule)
|
|
||||||
await profileModule.ChangeActiveProfileAnimated(null, Enumerable.Empty<ArtemisDevice>());
|
|
||||||
|
|
||||||
module.Deactivate(false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Error(new ArtemisPluginFeatureException(
|
|
||||||
module, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile"
|
|
||||||
);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OverrideActivate(Module module)
|
private void OverrideActivate(Module module)
|
||||||
@ -110,21 +55,15 @@ namespace Artemis.Core.Services
|
|||||||
// If activating while it should be deactivated, its an override
|
// If activating while it should be deactivated, its an override
|
||||||
bool shouldBeActivated = module.EvaluateActivationRequirements();
|
bool shouldBeActivated = module.EvaluateActivationRequirements();
|
||||||
module.Activate(!shouldBeActivated);
|
module.Activate(!shouldBeActivated);
|
||||||
|
|
||||||
// If this is a profile module, activate the last active profile after module activation
|
|
||||||
if (module is ProfileModule profileModule)
|
|
||||||
_profileService.ActivateLastProfile(profileModule);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.Error(new ArtemisPluginFeatureException(
|
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
|
||||||
module, "Failed to activate module and last profile.", e), "Failed to activate module and last profile"
|
|
||||||
);
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OverrideDeactivate(Module module, bool clearingOverride)
|
private void OverrideDeactivate(Module module)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -134,163 +73,132 @@ namespace Artemis.Core.Services
|
|||||||
// If deactivating while it should be activated, its an override
|
// If deactivating while it should be activated, its an override
|
||||||
bool shouldBeActivated = module.EvaluateActivationRequirements();
|
bool shouldBeActivated = module.EvaluateActivationRequirements();
|
||||||
// No need to deactivate if it is not in an overridden state
|
// No need to deactivate if it is not in an overridden state
|
||||||
if (shouldBeActivated && !module.IsActivatedOverride && !clearingOverride)
|
if (shouldBeActivated && !module.IsActivatedOverride)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
module.Deactivate(true);
|
module.Deactivate(true);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.Error(new ArtemisPluginFeatureException(
|
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to deactivate module.", e), "Failed to deactivate module");
|
||||||
module, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile"
|
|
||||||
);
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
|
private void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateModuleActivation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
|
||||||
|
{
|
||||||
|
lock (_updateLock)
|
||||||
|
{
|
||||||
|
if (e.PluginFeature is Module module && !_modules.Contains(module))
|
||||||
|
{
|
||||||
|
ImportDefaultProfiles(module);
|
||||||
|
_modules.Add(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
|
||||||
|
{
|
||||||
|
lock (_updateLock)
|
||||||
{
|
{
|
||||||
if (e.PluginFeature is Module module)
|
if (e.PluginFeature is Module module)
|
||||||
InitialiseOrApplyPriority(module);
|
_modules.Remove(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitialiseOrApplyPriority(Module module)
|
private void ImportDefaultProfiles(Module module)
|
||||||
{
|
|
||||||
ModulePriorityCategory category = module.DefaultPriorityCategory;
|
|
||||||
int priority = 1;
|
|
||||||
|
|
||||||
module.SettingsEntity = _moduleRepository.GetByModuleId(module.Id);
|
|
||||||
if (module.SettingsEntity != null)
|
|
||||||
{
|
|
||||||
category = (ModulePriorityCategory) module.SettingsEntity.PriorityCategory;
|
|
||||||
priority = module.SettingsEntity.Priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateModulePriority(module, category, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Module? ActiveModuleOverride { get; private set; }
|
|
||||||
|
|
||||||
public async Task SetActiveModuleOverride(Module? overrideModule)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ActiveModuleSemaphore.WaitAsync();
|
List<ProfileConfiguration> profileConfigurations = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).ToList();
|
||||||
|
foreach ((DefaultCategoryName categoryName, string profilePath) in module.DefaultProfilePaths)
|
||||||
if (ActiveModuleOverride == overrideModule)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (overrideModule != null)
|
|
||||||
{
|
{
|
||||||
OverrideActivate(overrideModule);
|
ProfileConfigurationExportModel? profileConfigurationExportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(File.ReadAllText(profilePath), IProfileService.ExportSettings);
|
||||||
_logger.Information($"Setting active module override to {overrideModule.DisplayName}");
|
if (profileConfigurationExportModel?.ProfileEntity == null)
|
||||||
|
throw new ArtemisCoreException($"Default profile at path {profilePath} contains no valid profile data");
|
||||||
|
if (profileConfigurations.Any(p => p.Entity.ProfileId == profileConfigurationExportModel.ProfileEntity.Id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == categoryName.ToString()) ??
|
||||||
|
_profileService.CreateProfileCategory(categoryName.ToString());
|
||||||
|
|
||||||
|
_profileService.ImportProfile(category, profileConfigurationExportModel, false, true, null);
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.Information("Clearing active module override");
|
_logger.Warning(e, "Failed to import default profiles for module {module}", module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always deactivate all other modules whenever override is called
|
public void UpdateModuleActivation()
|
||||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>().ToList();
|
|
||||||
foreach (Module module in modules.Where(m => m != overrideModule))
|
|
||||||
OverrideDeactivate(module, overrideModule != null);
|
|
||||||
|
|
||||||
ActiveModuleOverride = overrideModule;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
{
|
||||||
ActiveModuleSemaphore.Release();
|
lock (_updateLock)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateModuleActivation()
|
|
||||||
{
|
{
|
||||||
if (ActiveModuleSemaphore.CurrentCount == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ActiveModuleSemaphore.WaitAsync();
|
_activationUpdateTimer.Elapsed -= ActivationUpdateTimerOnElapsed;
|
||||||
|
foreach (Module module in _modules)
|
||||||
if (ActiveModuleOverride != null)
|
|
||||||
{
|
{
|
||||||
// The conditions of the active module override may be matched, in that case reactivate as a non-override
|
if (module.IsActivatedOverride)
|
||||||
// the principle is different for this service but not for the module
|
continue;
|
||||||
bool shouldBeActivated = ActiveModuleOverride.EvaluateActivationRequirements();
|
|
||||||
if (shouldBeActivated && ActiveModuleOverride.IsActivatedOverride)
|
|
||||||
ActiveModuleOverride.Reactivate(true, false);
|
|
||||||
else if (!shouldBeActivated && !ActiveModuleOverride.IsActivatedOverride) ActiveModuleOverride.Reactivate(false, true);
|
|
||||||
|
|
||||||
return;
|
if (module.IsAlwaysAvailable)
|
||||||
|
{
|
||||||
|
module.Activate(false);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stopwatch stopwatch = new();
|
module.Profiler.StartMeasurement("EvaluateActivationRequirements");
|
||||||
stopwatch.Start();
|
bool shouldBeActivated = module.IsEnabled && module.EvaluateActivationRequirements();
|
||||||
|
module.Profiler.StopMeasurement("EvaluateActivationRequirements");
|
||||||
|
|
||||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>().ToList();
|
|
||||||
List<Task> tasks = new();
|
|
||||||
foreach (Module module in modules)
|
|
||||||
{
|
|
||||||
lock (module)
|
|
||||||
{
|
|
||||||
bool shouldBeActivated = module.EvaluateActivationRequirements() && module.IsEnabled;
|
|
||||||
if (shouldBeActivated && !module.IsActivated)
|
if (shouldBeActivated && !module.IsActivated)
|
||||||
tasks.Add(ActivateModule(module));
|
{
|
||||||
|
module.Activate(false);
|
||||||
|
OnModuleActivated(new ModuleEventArgs(module));
|
||||||
|
}
|
||||||
else if (!shouldBeActivated && module.IsActivated)
|
else if (!shouldBeActivated && module.IsActivated)
|
||||||
tasks.Add(DeactivateModule(module));
|
{
|
||||||
|
module.Deactivate(false);
|
||||||
|
OnModuleDeactivated(new ModuleEventArgs(module));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
|
|
||||||
stopwatch.Stop();
|
|
||||||
if (stopwatch.ElapsedMilliseconds > 100 && !tasks.Any())
|
|
||||||
_logger.Warning("Activation requirements evaluation took too long: {moduleCount} module(s) in {elapsed}", modules.Count, stopwatch.Elapsed);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ActiveModuleSemaphore.Release();
|
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority)
|
public void SetActivationOverride(Module? module)
|
||||||
{
|
{
|
||||||
if (module.PriorityCategory == category && module.Priority == priority)
|
lock (_updateLock)
|
||||||
return;
|
|
||||||
|
|
||||||
List<Module> modules = _pluginManagementService
|
|
||||||
.GetFeaturesOfType<Module>()
|
|
||||||
.Where(m => m.PriorityCategory == category)
|
|
||||||
.OrderBy(m => m.Priority)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (modules.Contains(module))
|
|
||||||
modules.Remove(module);
|
|
||||||
|
|
||||||
priority = Math.Min(modules.Count, Math.Max(0, priority));
|
|
||||||
modules.Insert(priority, module);
|
|
||||||
|
|
||||||
module.PriorityCategory = category;
|
|
||||||
for (int index = 0; index < modules.Count; index++)
|
|
||||||
{
|
{
|
||||||
Module categoryModule = modules[index];
|
if (_activationOverride != null)
|
||||||
categoryModule.Priority = index;
|
OverrideDeactivate(_activationOverride);
|
||||||
|
_activationOverride = module;
|
||||||
|
if (_activationOverride != null)
|
||||||
|
OverrideActivate(_activationOverride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't save modules whose priority hasn't been initialized yet
|
public void UpdateActiveModules(double deltaTime)
|
||||||
if (categoryModule == module || categoryModule.SettingsEntity != null)
|
|
||||||
{
|
{
|
||||||
categoryModule.ApplyToEntity();
|
lock (_updateLock)
|
||||||
_moduleRepository.Save(categoryModule.SettingsEntity);
|
{
|
||||||
|
foreach (Module module in _modules)
|
||||||
|
module.InternalUpdate(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModulePriorityUpdated?.Invoke(this, EventArgs.Empty);
|
public event EventHandler<ModuleEventArgs>? ModuleActivated;
|
||||||
}
|
public event EventHandler<ModuleEventArgs>? ModuleDeactivated;
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
public event EventHandler? ModulePriorityUpdated;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,8 +13,6 @@ namespace Artemis.Core.Services
|
|||||||
// Add data models of already loaded plugins
|
// Add data models of already loaded plugins
|
||||||
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled))
|
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled))
|
||||||
AddModuleDataModel(module);
|
AddModuleDataModel(module);
|
||||||
foreach (BaseDataModelExpansion dataModelExpansion in pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled))
|
|
||||||
AddDataModelExpansionDataModel(dataModelExpansion);
|
|
||||||
|
|
||||||
// Add data models of new plugins when they get enabled
|
// Add data models of new plugins when they get enabled
|
||||||
pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
|
pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
|
||||||
@ -54,8 +52,6 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
if (e.PluginFeature is Module module)
|
if (e.PluginFeature is Module module)
|
||||||
AddModuleDataModel(module);
|
AddModuleDataModel(module);
|
||||||
else if (e.PluginFeature is BaseDataModelExpansion dataModelExpansion)
|
|
||||||
AddDataModelExpansionDataModel(dataModelExpansion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddModuleDataModel(Module module)
|
private void AddModuleDataModel(Module module)
|
||||||
@ -66,19 +62,8 @@ namespace Artemis.Core.Services
|
|||||||
if (module.InternalDataModel.DataModelDescription == null)
|
if (module.InternalDataModel.DataModelDescription == null)
|
||||||
throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null");
|
throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null");
|
||||||
|
|
||||||
module.InternalDataModel.IsExpansion = module.InternalExpandsMainDataModel;
|
module.InternalDataModel.IsExpansion = module.IsAlwaysAvailable;
|
||||||
RegisterDataModel(module.InternalDataModel);
|
RegisterDataModel(module.InternalDataModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion)
|
|
||||||
{
|
|
||||||
if (dataModelExpansion.InternalDataModel == null)
|
|
||||||
throw new ArtemisCoreException("Cannot add data model expansion that is not enabled");
|
|
||||||
if (dataModelExpansion.InternalDataModel.DataModelDescription == null)
|
|
||||||
throw new ArtemisPluginFeatureException(dataModelExpansion, "Data model expansion overrides GetDataModelDescription but returned null");
|
|
||||||
|
|
||||||
dataModelExpansion.InternalDataModel.IsExpansion = true;
|
|
||||||
RegisterDataModel(dataModelExpansion.InternalDataModel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
||||||
using System.Threading.Tasks;
|
using Newtonsoft.Json;
|
||||||
using Artemis.Core.Modules;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core.Services
|
namespace Artemis.Core.Services
|
||||||
{
|
{
|
||||||
@ -10,122 +10,144 @@ namespace Artemis.Core.Services
|
|||||||
public interface IProfileService : IArtemisService
|
public interface IProfileService : IArtemisService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new profile for the given module and returns a descriptor pointing to it
|
/// Gets the JSON serializer settings used to create profile mementos
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="module">The profile module to create the profile for</param>
|
public static JsonSerializerSettings MementoSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All};
|
||||||
/// <param name="name">The name of the new profile</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a descriptor for each profile stored for the given <see cref="ProfileModule" />
|
/// Gets the JSON serializer settings used to import/export profiles
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="module">The module to return profile descriptors for</param>
|
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
|
||||||
/// <returns></returns>
|
|
||||||
List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module);
|
/// <summary>
|
||||||
|
/// Gets a read only collection containing all the profile categories
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read only collection containing all the profile configurations
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited
|
||||||
|
/// </summary>
|
||||||
|
bool RenderForEditor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
|
||||||
|
Profile ActivateProfile(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deactivates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
|
||||||
|
void DeactivateProfile(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permanently deletes the profile of the given <see cref="ProfileConfiguration" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileConfiguration">The profile configuration of the profile to delete</param>
|
||||||
|
void DeleteProfile(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the
|
||||||
|
/// <see cref="Profile" />s themselves
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileCategory">The profile category to update</param>
|
||||||
|
void SaveProfileCategory(ProfileCategory profileCategory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new profile category and saves it to persistent storage
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the new profile category, must be unique</param>
|
||||||
|
/// <returns>The newly created profile category</returns>
|
||||||
|
ProfileCategory CreateProfileCategory(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permanently deletes the provided profile category
|
||||||
|
/// </summary>
|
||||||
|
void DeleteProfileCategory(ProfileCategory profileCategory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category">The profile category to add the profile to</param>
|
||||||
|
/// <param name="name">The name of the new profile configuration</param>
|
||||||
|
/// <param name="icon">The icon of the new profile configuration</param>
|
||||||
|
/// <returns>The newly created profile configuration</returns>
|
||||||
|
ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileConfiguration"></param>
|
||||||
|
void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the icon of this profile configuration if needed and puts it into <c>ProfileConfiguration.Icon.FileIcon</c>
|
||||||
|
/// </summary>
|
||||||
|
void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the current icon of this profile
|
||||||
|
/// </summary>
|
||||||
|
void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the profile to persistent storage
|
/// Writes the profile to persistent storage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile"></param>
|
/// <param name="profile"></param>
|
||||||
/// <param name="includeChildren"></param>
|
/// <param name="includeChildren"></param>
|
||||||
void UpdateProfile(Profile profile, bool includeChildren);
|
void SaveProfile(Profile profile, bool includeChildren);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes and permanently deletes the provided profile
|
/// Attempts to restore the profile to the state it had before the last <see cref="SaveProfile" /> call.
|
||||||
/// </summary>
|
|
||||||
/// <param name="profile">The profile to delete</param>
|
|
||||||
void DeleteProfile(Profile profile);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Permanently deletes the profile described by the provided profile descriptor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="profileDescriptor">The descriptor pointing to the profile to delete</param>
|
|
||||||
void DeleteProfile(ProfileDescriptor profileDescriptor);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Activates the last profile of the given profile module
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="profileModule"></param>
|
|
||||||
void ActivateLastProfile(ProfileModule profileModule);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reloads the currently active profile on the provided profile module
|
|
||||||
/// </summary>
|
|
||||||
void ReloadProfile(ProfileModule module);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously activates the last profile of the given profile module using a fade animation
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="profileModule"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task ActivateLastProfileAnimated(ProfileModule profileModule);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Activates the profile described in the given <see cref="ProfileDescriptor" /> with the currently active surface
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="profileDescriptor">The descriptor describing the profile to activate</param>
|
|
||||||
Profile ActivateProfile(ProfileDescriptor profileDescriptor);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously activates the profile described in the given <see cref="ProfileDescriptor" /> with the currently
|
|
||||||
/// active surface using a fade animation
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="profileDescriptor">The descriptor describing the profile to activate</param>
|
|
||||||
Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the active profile on the given <see cref="ProfileModule" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="module">The profile module to deactivate the active profile on</param>
|
|
||||||
void ClearActiveProfile(ProfileModule module);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously clears the active profile on the given <see cref="ProfileModule" /> using a fade animation
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="module">The profile module to deactivate the active profile on</param>
|
|
||||||
Task ClearActiveProfileAnimated(ProfileModule module);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to restore the profile to the state it had before the last <see cref="UpdateProfile" /> call.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile"></param>
|
/// <param name="profile"></param>
|
||||||
bool UndoUpdateProfile(Profile profile);
|
bool UndoSaveProfile(Profile profile);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to restore the profile to the state it had before the last <see cref="UndoUpdateProfile" /> call.
|
/// Attempts to restore the profile to the state it had before the last <see cref="UndoSaveProfile" /> call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile"></param>
|
/// <param name="profile"></param>
|
||||||
bool RedoUpdateProfile(Profile profile);
|
bool RedoSaveProfile(Profile profile);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prepares the profile for rendering. You should not need to call this, it is exposed for some niche usage in the
|
/// Exports the profile described in the given <see cref="ProfileConfiguration" /> into an export model
|
||||||
/// core
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile"></param>
|
/// <param name="profileConfiguration">The profile configuration of the profile to export</param>
|
||||||
void InstantiateProfile(Profile profile);
|
/// <returns>The resulting export model</returns>
|
||||||
|
ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [Placeholder] Exports the profile described in the given <see cref="ProfileDescriptor" /> in a JSON format
|
/// Imports the provided base64 encoded GZIPed JSON as a profile configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profileDescriptor">The descriptor of the profile to export</param>
|
/// <param name="category">The <see cref="ProfileCategory" /> in which to import the profile</param>
|
||||||
/// <returns>The resulting JSON</returns>
|
/// <param name="exportModel">The model containing the profile to import</param>
|
||||||
string ExportProfile(ProfileDescriptor profileDescriptor);
|
/// <param name="makeUnique">Whether or not to give the profile a new GUID, making it unique</param>
|
||||||
|
/// <param name="markAsFreshImport">Whether or not to mark the profile as a fresh import, causing it to be adapted until any changes are made to it</param>
|
||||||
/// <summary>
|
|
||||||
/// [Placeholder] Imports the provided base64 encoded GZIPed JSON as a profile for the given
|
|
||||||
/// <see cref="ProfileModule" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json">The content of the profile as JSON</param>
|
|
||||||
/// <param name="profileModule">The module to import the profile in to</param>
|
|
||||||
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param>
|
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param>
|
||||||
/// <returns></returns>
|
/// <returns>The resulting profile configuration</returns>
|
||||||
ProfileDescriptor ImportProfile(string json, ProfileModule profileModule, string nameAffix = "imported");
|
ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique = true, bool markAsFreshImport = true, string? nameAffix = "imported");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adapts a given profile to the currently active devices
|
/// Adapts a given profile to the currently active devices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile">The profile to adapt</param>
|
/// <param name="profile">The profile to adapt</param>
|
||||||
void AdaptProfile(Profile profile);
|
void AdaptProfile(Profile profile);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates all currently active profiles
|
||||||
|
/// </summary>
|
||||||
|
void UpdateProfiles(double deltaTime);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders all currently active profiles
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="canvas"></param>
|
||||||
|
void RenderProfiles(SKCanvas canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core.Services
|
namespace Artemis.Core.Services
|
||||||
{
|
{
|
||||||
@ -14,48 +15,38 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly IRgbService _rgbService;
|
private readonly List<ProfileCategory> _profileCategories;
|
||||||
|
private readonly IProfileCategoryRepository _profileCategoryRepository;
|
||||||
private readonly IProfileRepository _profileRepository;
|
private readonly IProfileRepository _profileRepository;
|
||||||
|
private readonly IRgbService _rgbService;
|
||||||
|
|
||||||
|
private readonly List<Exception> _updateExceptions = new();
|
||||||
|
private DateTime _lastUpdateExceptionLog;
|
||||||
|
private readonly List<Exception> _renderExceptions = new();
|
||||||
|
private DateTime _lastRenderExceptionLog;
|
||||||
|
|
||||||
public ProfileService(ILogger logger,
|
public ProfileService(ILogger logger,
|
||||||
IPluginManagementService pluginManagementService,
|
|
||||||
IRgbService rgbService,
|
IRgbService rgbService,
|
||||||
|
// TODO: Move these two
|
||||||
IConditionOperatorService conditionOperatorService,
|
IConditionOperatorService conditionOperatorService,
|
||||||
IDataBindingService dataBindingService,
|
IDataBindingService dataBindingService,
|
||||||
|
IProfileCategoryRepository profileCategoryRepository,
|
||||||
|
IPluginManagementService pluginManagementService,
|
||||||
IProfileRepository profileRepository)
|
IProfileRepository profileRepository)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_pluginManagementService = pluginManagementService;
|
|
||||||
_rgbService = rgbService;
|
_rgbService = rgbService;
|
||||||
|
_profileCategoryRepository = profileCategoryRepository;
|
||||||
|
_pluginManagementService = pluginManagementService;
|
||||||
_profileRepository = profileRepository;
|
_profileRepository = profileRepository;
|
||||||
|
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
|
||||||
|
|
||||||
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
|
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
|
||||||
}
|
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||||
|
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||||
|
|
||||||
public static JsonSerializerSettings MementoSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All};
|
if (!_profileCategories.Any())
|
||||||
public static JsonSerializerSettings ExportSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
|
CreateDefaultProfileCategories();
|
||||||
|
|
||||||
public ProfileDescriptor? GetLastActiveProfile(ProfileModule module)
|
|
||||||
{
|
|
||||||
List<ProfileEntity> moduleProfiles = _profileRepository.GetByModuleId(module.Id);
|
|
||||||
if (!moduleProfiles.Any())
|
|
||||||
return CreateProfileDescriptor(module, "Default");
|
|
||||||
|
|
||||||
ProfileEntity? profileEntity = moduleProfiles.FirstOrDefault(p => p.IsActive) ?? moduleProfiles.FirstOrDefault();
|
|
||||||
return profileEntity == null ? null : new ProfileDescriptor(module, profileEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveActiveProfile(ProfileModule module)
|
|
||||||
{
|
|
||||||
if (module.ActiveProfile == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
List<ProfileEntity> profileEntities = _profileRepository.GetByModuleId(module.Id);
|
|
||||||
foreach (ProfileEntity profileEntity in profileEntities)
|
|
||||||
{
|
|
||||||
profileEntity.IsActive = module.ActiveProfile.EntityId == profileEntity.Id;
|
|
||||||
_profileRepository.Save(profileEntity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -63,168 +54,324 @@ namespace Artemis.Core.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ActiveProfilesPopulateLeds()
|
private void ActiveProfilesPopulateLeds()
|
||||||
{
|
{
|
||||||
List<ProfileModule> profileModules = _pluginManagementService.GetFeaturesOfType<ProfileModule>();
|
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||||
foreach (ProfileModule profileModule in profileModules)
|
|
||||||
{
|
{
|
||||||
// Avoid race condition, make the check here
|
if (profileConfiguration.Profile == null) continue;
|
||||||
if (profileModule.ActiveProfile == null)
|
profileConfiguration.Profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||||
|
|
||||||
|
if (!profileConfiguration.Profile.IsFreshImport) continue;
|
||||||
|
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile);
|
||||||
|
AdaptProfile(profileConfiguration.Profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateModules()
|
||||||
|
{
|
||||||
|
lock (_profileRepository)
|
||||||
|
{
|
||||||
|
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
|
||||||
|
foreach (ProfileCategory profileCategory in _profileCategories)
|
||||||
|
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
|
||||||
|
profileConfiguration.LoadModules(modules);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ActiveProfilesPopulateLeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PluginFeature is Module)
|
||||||
|
UpdateModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RenderForEditor { get; set; }
|
||||||
|
|
||||||
|
public void UpdateProfiles(double deltaTime)
|
||||||
|
{
|
||||||
|
lock (_profileCategories)
|
||||||
|
{
|
||||||
|
// Iterate the children in reverse because the first category must be rendered last to end up on top
|
||||||
|
for (int i = _profileCategories.Count - 1; i > -1; i--)
|
||||||
|
{
|
||||||
|
ProfileCategory profileCategory = _profileCategories[i];
|
||||||
|
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||||
|
{
|
||||||
|
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||||
|
// Profiles being edited are updated at their own leisure
|
||||||
|
if (profileConfiguration.IsBeingEdited)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
profileModule.ActiveProfile.PopulateLeds(_rgbService.EnabledDevices);
|
bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
|
||||||
if (profileModule.ActiveProfile.IsFreshImport)
|
if (shouldBeActive)
|
||||||
{
|
{
|
||||||
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileModule.ActiveProfile);
|
profileConfiguration.Update();
|
||||||
AdaptProfile(profileModule.ActiveProfile);
|
shouldBeActive = profileConfiguration.ActivationConditionMet;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module)
|
try
|
||||||
{
|
{
|
||||||
List<ProfileEntity> profileEntities = _profileRepository.GetByModuleId(module.Id);
|
// Make sure the profile is active or inactive according to the parameters above
|
||||||
return profileEntities.Select(e => new ProfileDescriptor(module, e)).ToList();
|
if (shouldBeActive && profileConfiguration.Profile == null)
|
||||||
|
ActivateProfile(profileConfiguration);
|
||||||
|
else if (!shouldBeActive && profileConfiguration.Profile != null)
|
||||||
|
DeactivateProfile(profileConfiguration);
|
||||||
|
|
||||||
|
profileConfiguration.Profile?.Update(deltaTime);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_updateExceptions.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name)
|
LogProfileUpdateExceptions();
|
||||||
{
|
}
|
||||||
ProfileEntity profileEntity = new() {Id = Guid.NewGuid(), Name = name, ModuleId = module.Id};
|
|
||||||
_profileRepository.Add(profileEntity);
|
|
||||||
|
|
||||||
return new ProfileDescriptor(module, profileEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ActivateLastProfile(ProfileModule profileModule)
|
public void RenderProfiles(SKCanvas canvas)
|
||||||
{
|
{
|
||||||
ProfileDescriptor? activeProfile = GetLastActiveProfile(profileModule);
|
lock (_profileCategories)
|
||||||
if (activeProfile != null)
|
{
|
||||||
ActivateProfile(activeProfile);
|
// Iterate the children in reverse because the first category must be rendered last to end up on top
|
||||||
|
for (int i = _profileCategories.Count - 1; i > -1; i--)
|
||||||
|
{
|
||||||
|
ProfileCategory profileCategory = _profileCategories[i];
|
||||||
|
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||||
|
if (RenderForEditor)
|
||||||
|
{
|
||||||
|
if (profileConfiguration.IsBeingEdited)
|
||||||
|
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ActivateLastProfileAnimated(ProfileModule profileModule)
|
|
||||||
{
|
|
||||||
ProfileDescriptor? activeProfile = GetLastActiveProfile(profileModule);
|
|
||||||
if (activeProfile != null)
|
|
||||||
await ActivateProfileAnimated(activeProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Profile ActivateProfile(ProfileDescriptor profileDescriptor)
|
|
||||||
{
|
|
||||||
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
|
|
||||||
return profileDescriptor.ProfileModule.ActiveProfile;
|
|
||||||
|
|
||||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
|
||||||
if (profileEntity == null)
|
|
||||||
throw new ArtemisCoreException($"Cannot find profile named: {profileDescriptor.Name} ID: {profileDescriptor.Id}");
|
|
||||||
|
|
||||||
Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
|
|
||||||
InstantiateProfile(profile);
|
|
||||||
|
|
||||||
profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _rgbService.EnabledDevices);
|
|
||||||
if (profile.IsFreshImport)
|
|
||||||
{
|
|
||||||
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profile);
|
|
||||||
AdaptProfile(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveActiveProfile(profileDescriptor.ProfileModule);
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadProfile(ProfileModule module)
|
|
||||||
{
|
|
||||||
if (module.ActiveProfile == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId);
|
|
||||||
Profile profile = new(module, entity);
|
|
||||||
InstantiateProfile(profile);
|
|
||||||
|
|
||||||
module.ChangeActiveProfile(null, _rgbService.EnabledDevices);
|
|
||||||
module.ChangeActiveProfile(profile, _rgbService.EnabledDevices);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
|
|
||||||
{
|
|
||||||
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
|
|
||||||
return profileDescriptor.ProfileModule.ActiveProfile;
|
|
||||||
|
|
||||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
|
||||||
if (profileEntity == null)
|
|
||||||
throw new ArtemisCoreException($"Cannot find profile named: {profileDescriptor.Name} ID: {profileDescriptor.Id}");
|
|
||||||
|
|
||||||
Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
|
|
||||||
InstantiateProfile(profile);
|
|
||||||
|
|
||||||
void ActivatingRgbServiceOnLedsChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivatingProfilePluginToggle(object? sender, PluginEventArgs e)
|
|
||||||
{
|
|
||||||
if (!profile.Disposed)
|
|
||||||
InstantiateProfile(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This could happen during activation so subscribe to it
|
|
||||||
_pluginManagementService.PluginEnabled += ActivatingProfilePluginToggle;
|
|
||||||
_pluginManagementService.PluginDisabled += ActivatingProfilePluginToggle;
|
|
||||||
_rgbService.LedsChanged += ActivatingRgbServiceOnLedsChanged;
|
|
||||||
|
|
||||||
await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _rgbService.EnabledDevices);
|
|
||||||
if (profile.IsFreshImport)
|
|
||||||
{
|
|
||||||
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profile);
|
|
||||||
AdaptProfile(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveActiveProfile(profileDescriptor.ProfileModule);
|
|
||||||
|
|
||||||
_pluginManagementService.PluginEnabled -= ActivatingProfilePluginToggle;
|
|
||||||
_pluginManagementService.PluginDisabled -= ActivatingProfilePluginToggle;
|
|
||||||
_rgbService.LedsChanged -= ActivatingRgbServiceOnLedsChanged;
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void ClearActiveProfile(ProfileModule module)
|
|
||||||
{
|
|
||||||
module.ChangeActiveProfile(null, _rgbService.EnabledDevices);
|
|
||||||
SaveActiveProfile(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClearActiveProfileAnimated(ProfileModule module)
|
|
||||||
{
|
|
||||||
await module.ChangeActiveProfileAnimated(null, _rgbService.EnabledDevices);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteProfile(Profile profile)
|
|
||||||
{
|
|
||||||
_logger.Debug("Removing profile " + profile);
|
|
||||||
|
|
||||||
// If the given profile is currently active, disable it first (this also disposes it)
|
|
||||||
if (profile.Module.ActiveProfile == profile)
|
|
||||||
ClearActiveProfile(profile.Module);
|
|
||||||
else
|
else
|
||||||
profile.Dispose();
|
{
|
||||||
|
// Ensure all criteria are met before rendering
|
||||||
_profileRepository.Remove(profile.ProfileEntity);
|
if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet)
|
||||||
|
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_renderExceptions.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteProfile(ProfileDescriptor profileDescriptor)
|
LogProfileRenderExceptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateDefaultProfileCategories()
|
||||||
{
|
{
|
||||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
foreach (DefaultCategoryName defaultCategoryName in Enum.GetValues<DefaultCategoryName>())
|
||||||
|
CreateProfileCategory(defaultCategoryName.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogProfileUpdateExceptions()
|
||||||
|
{
|
||||||
|
// Only log update exceptions every 10 seconds to avoid spamming the logs
|
||||||
|
if (DateTime.Now - _lastUpdateExceptionLog < TimeSpan.FromSeconds(10))
|
||||||
|
return;
|
||||||
|
_lastUpdateExceptionLog = DateTime.Now;
|
||||||
|
|
||||||
|
if (!_updateExceptions.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Group by stack trace, that should gather up duplicate exceptions
|
||||||
|
foreach (IGrouping<string?, Exception> exceptions in _updateExceptions.GroupBy(e => e.StackTrace))
|
||||||
|
_logger.Warning(exceptions.First(), "Exception was thrown {count} times during profile update in the last 10 seconds", exceptions.Count());
|
||||||
|
|
||||||
|
// When logging is finished start with a fresh slate
|
||||||
|
_updateExceptions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogProfileRenderExceptions()
|
||||||
|
{
|
||||||
|
// Only log update exceptions every 10 seconds to avoid spamming the logs
|
||||||
|
if (DateTime.Now - _lastRenderExceptionLog < TimeSpan.FromSeconds(10))
|
||||||
|
return;
|
||||||
|
_lastRenderExceptionLog = DateTime.Now;
|
||||||
|
|
||||||
|
if (!_renderExceptions.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Group by stack trace, that should gather up duplicate exceptions
|
||||||
|
foreach (IGrouping<string?, Exception> exceptions in _renderExceptions.GroupBy(e => e.StackTrace))
|
||||||
|
_logger.Warning(exceptions.First(), "Exception was thrown {count} times during profile render in the last 10 seconds", exceptions.Count());
|
||||||
|
|
||||||
|
// When logging is finished start with a fresh slate
|
||||||
|
_renderExceptions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<ProfileCategory> ProfileCategories
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_profileRepository)
|
||||||
|
{
|
||||||
|
return _profileCategories.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_profileRepository)
|
||||||
|
{
|
||||||
|
return _profileCategories.SelectMany(c => c.ProfileConfigurations).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||||
|
return;
|
||||||
|
if (profileConfiguration.Icon.FileIcon != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
profileConfiguration.Icon.FileIcon = _profileCategoryRepository.GetProfileIconStream(profileConfiguration.Entity.FileIconId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (profileConfiguration.Icon.FileIcon != null)
|
||||||
|
{
|
||||||
|
profileConfiguration.Icon.FileIcon.Position = 0;
|
||||||
|
_profileCategoryRepository.SaveProfileIconStream(profileConfiguration.Entity, profileConfiguration.Icon.FileIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Profile ActivateProfile(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
if (profileConfiguration.Profile != null)
|
||||||
|
return profileConfiguration.Profile;
|
||||||
|
|
||||||
|
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||||
|
if (profileEntity == null)
|
||||||
|
throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}");
|
||||||
|
|
||||||
|
Profile profile = new(profileConfiguration, profileEntity);
|
||||||
|
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||||
|
|
||||||
|
if (profile.IsFreshImport)
|
||||||
|
{
|
||||||
|
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profile);
|
||||||
|
AdaptProfile(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
profileConfiguration.Profile = profile;
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeactivateProfile(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
if (profileConfiguration.IsBeingEdited)
|
||||||
|
throw new ArtemisCoreException("Cannot disable a profile that is being edited, that's rude");
|
||||||
|
if (profileConfiguration.Profile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Profile profile = profileConfiguration.Profile;
|
||||||
|
profileConfiguration.Profile = null;
|
||||||
|
profile.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteProfile(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
DeactivateProfile(profileConfiguration);
|
||||||
|
|
||||||
|
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||||
if (profileEntity == null)
|
if (profileEntity == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
|
||||||
|
_profileRepository.Remove(profileEntity);
|
||||||
|
SaveProfileCategory(profileConfiguration.Category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileCategory CreateProfileCategory(string name)
|
||||||
|
{
|
||||||
|
lock (_profileRepository)
|
||||||
|
{
|
||||||
|
ProfileCategory profileCategory = new(name);
|
||||||
|
_profileCategories.Add(profileCategory);
|
||||||
|
SaveProfileCategory(profileCategory);
|
||||||
|
return profileCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteProfileCategory(ProfileCategory profileCategory)
|
||||||
|
{
|
||||||
|
List<ProfileConfiguration> profileConfigurations = profileCategory.ProfileConfigurations.ToList();
|
||||||
|
foreach (ProfileConfiguration profileConfiguration in profileConfigurations)
|
||||||
|
RemoveProfileConfiguration(profileConfiguration);
|
||||||
|
|
||||||
|
lock (_profileRepository)
|
||||||
|
{
|
||||||
|
_profileCategories.Remove(profileCategory);
|
||||||
|
_profileCategoryRepository.Remove(profileCategory.Entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category">The profile category to add the profile to</param>
|
||||||
|
/// <param name="name">The name of the new profile configuration</param>
|
||||||
|
/// <param name="icon">The icon of the new profile configuration</param>
|
||||||
|
/// <returns>The newly created profile configuration</returns>
|
||||||
|
public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon)
|
||||||
|
{
|
||||||
|
ProfileConfiguration configuration = new(category, name, icon);
|
||||||
|
ProfileEntity entity = new();
|
||||||
|
_profileRepository.Add(entity);
|
||||||
|
|
||||||
|
configuration.Entity.ProfileId = entity.Id;
|
||||||
|
category.AddProfileConfiguration(configuration, 0);
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileConfiguration"></param>
|
||||||
|
public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
|
||||||
|
|
||||||
|
DeactivateProfile(profileConfiguration);
|
||||||
|
SaveProfileCategory(profileConfiguration.Category);
|
||||||
|
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||||
|
if (profileEntity != null)
|
||||||
_profileRepository.Remove(profileEntity);
|
_profileRepository.Remove(profileEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProfile(Profile profile, bool includeChildren)
|
public void SaveProfileCategory(ProfileCategory profileCategory)
|
||||||
{
|
{
|
||||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
profileCategory.Save();
|
||||||
|
_profileCategoryRepository.Save(profileCategory.Entity);
|
||||||
|
|
||||||
|
lock (_profileCategories)
|
||||||
|
{
|
||||||
|
_profileCategories.Sort((a, b) => a.Order - b.Order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveProfile(Profile profile, bool includeChildren)
|
||||||
|
{
|
||||||
|
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||||
profile.Save();
|
profile.Save();
|
||||||
if (includeChildren)
|
if (includeChildren)
|
||||||
{
|
{
|
||||||
@ -235,7 +382,7 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If there are no changes, don't bother saving
|
// If there are no changes, don't bother saving
|
||||||
string updatedMemento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
string updatedMemento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||||
if (memento.Equals(updatedMemento))
|
if (memento.Equals(updatedMemento))
|
||||||
{
|
{
|
||||||
_logger.Debug("Updating profile - Skipping save, no changes");
|
_logger.Debug("Updating profile - Skipping save, no changes");
|
||||||
@ -253,7 +400,7 @@ namespace Artemis.Core.Services
|
|||||||
_profileRepository.Save(profile.ProfileEntity);
|
_profileRepository.Save(profile.ProfileEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UndoUpdateProfile(Profile profile)
|
public bool UndoSaveProfile(Profile profile)
|
||||||
{
|
{
|
||||||
// Keep the profile from being rendered by locking it
|
// Keep the profile from being rendered by locking it
|
||||||
lock (profile)
|
lock (profile)
|
||||||
@ -265,20 +412,20 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
string top = profile.UndoStack.Pop();
|
string top = profile.UndoStack.Pop();
|
||||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||||
profile.RedoStack.Push(memento);
|
profile.RedoStack.Push(memento);
|
||||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings)
|
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, IProfileService.MementoSettings)
|
||||||
?? throw new InvalidOperationException("Failed to deserialize memento");
|
?? throw new InvalidOperationException("Failed to deserialize memento");
|
||||||
|
|
||||||
profile.Load();
|
profile.Load();
|
||||||
InstantiateProfile(profile);
|
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug("Undo profile update - Success");
|
_logger.Debug("Undo profile update - Success");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RedoUpdateProfile(Profile profile)
|
public bool RedoSaveProfile(Profile profile)
|
||||||
{
|
{
|
||||||
// Keep the profile from being rendered by locking it
|
// Keep the profile from being rendered by locking it
|
||||||
lock (profile)
|
lock (profile)
|
||||||
@ -290,53 +437,81 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
string top = profile.RedoStack.Pop();
|
string top = profile.RedoStack.Pop();
|
||||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||||
profile.UndoStack.Push(memento);
|
profile.UndoStack.Push(memento);
|
||||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings)
|
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, IProfileService.MementoSettings)
|
||||||
?? throw new InvalidOperationException("Failed to deserialize memento");
|
?? throw new InvalidOperationException("Failed to deserialize memento");
|
||||||
|
|
||||||
profile.Load();
|
profile.Load();
|
||||||
InstantiateProfile(profile);
|
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||||
|
|
||||||
_logger.Debug("Redo profile update - Success");
|
_logger.Debug("Redo profile update - Success");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InstantiateProfile(Profile profile)
|
public ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration)
|
||||||
{
|
{
|
||||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
// The profile may not be active and in that case lets activate it real quick
|
||||||
|
Profile profile = profileConfiguration.Profile ?? ActivateProfile(profileConfiguration);
|
||||||
|
|
||||||
|
return new ProfileConfigurationExportModel
|
||||||
|
{
|
||||||
|
ProfileConfigurationEntity = profileConfiguration.Entity,
|
||||||
|
ProfileEntity = profile.ProfileEntity,
|
||||||
|
ProfileImage = profileConfiguration.Icon.FileIcon
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExportProfile(ProfileDescriptor profileDescriptor)
|
public ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique, bool markAsFreshImport, string? nameAffix)
|
||||||
{
|
{
|
||||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
if (exportModel.ProfileEntity == null)
|
||||||
if (profileEntity == null)
|
throw new ArtemisCoreException("Cannot import a profile without any data");
|
||||||
throw new ArtemisCoreException($"Cannot find profile named: {profileDescriptor.Name} ID: {profileDescriptor.Id}");
|
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(profileEntity, ExportSettings);
|
// Create a copy of the entity because we'll be using it from now on
|
||||||
}
|
ProfileEntity profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(
|
||||||
|
JsonConvert.SerializeObject(exportModel.ProfileEntity, IProfileService.ExportSettings), IProfileService.ExportSettings
|
||||||
public ProfileDescriptor ImportProfile(string json, ProfileModule profileModule, string nameAffix)
|
)!;
|
||||||
{
|
|
||||||
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(json, ExportSettings);
|
|
||||||
if (profileEntity == null)
|
|
||||||
throw new ArtemisCoreException("Failed to import profile but JSON.NET threw no error :(");
|
|
||||||
|
|
||||||
// Assign a new GUID to make sure it is unique in case of a previous import of the same content
|
// Assign a new GUID to make sure it is unique in case of a previous import of the same content
|
||||||
|
if (makeUnique)
|
||||||
profileEntity.UpdateGuid(Guid.NewGuid());
|
profileEntity.UpdateGuid(Guid.NewGuid());
|
||||||
|
|
||||||
|
if (nameAffix != null)
|
||||||
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
||||||
|
if (markAsFreshImport)
|
||||||
profileEntity.IsFreshImport = true;
|
profileEntity.IsFreshImport = true;
|
||||||
profileEntity.IsActive = false;
|
|
||||||
|
|
||||||
_profileRepository.Add(profileEntity);
|
_profileRepository.Add(profileEntity);
|
||||||
return new ProfileDescriptor(profileModule, profileEntity);
|
|
||||||
|
ProfileConfiguration profileConfiguration;
|
||||||
|
if (exportModel.ProfileConfigurationEntity != null)
|
||||||
|
{
|
||||||
|
// A new GUID will be given on save
|
||||||
|
exportModel.ProfileConfigurationEntity.FileIconId = Guid.Empty;
|
||||||
|
profileConfiguration = new ProfileConfiguration(category, exportModel.ProfileConfigurationEntity);
|
||||||
|
if (nameAffix != null)
|
||||||
|
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
profileConfiguration = new ProfileConfiguration(category, exportModel.ProfileEntity!.Name, "Import");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportModel.ProfileImage != null)
|
||||||
|
profileConfiguration.Icon.FileIcon = exportModel.ProfileImage;
|
||||||
|
|
||||||
|
profileConfiguration.Entity.ProfileId = profileEntity.Id;
|
||||||
|
category.AddProfileConfiguration(profileConfiguration, 0);
|
||||||
|
SaveProfileCategory(category);
|
||||||
|
|
||||||
|
return profileConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void AdaptProfile(Profile profile)
|
public void AdaptProfile(Profile profile)
|
||||||
{
|
{
|
||||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||||
|
|
||||||
List<ArtemisDevice> devices = _rgbService.EnabledDevices.ToList();
|
List<ArtemisDevice> devices = _rgbService.EnabledDevices.ToList();
|
||||||
foreach (Layer layer in profile.GetAllLayers())
|
foreach (Layer layer in profile.GetAllLayers())
|
||||||
@ -355,14 +530,5 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
_profileRepository.Save(profile.ProfileEntity);
|
_profileRepository.Save(profile.ProfileEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Event handlers
|
|
||||||
|
|
||||||
private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
ActiveProfilesPopulateLeds();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,17 +15,7 @@ namespace Artemis.Core.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
|
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
|
||||||
{
|
{
|
||||||
private readonly ProfileModule<T>? _profileModule;
|
private readonly Module<T> _module;
|
||||||
private readonly Module<T>? _module;
|
|
||||||
private readonly DataModelExpansion<T>? _dataModelExpansion;
|
|
||||||
|
|
||||||
internal DataModelJsonPluginEndPoint(ProfileModule<T> profileModule, string name, PluginsModule pluginsModule) : base(profileModule, name, pluginsModule)
|
|
||||||
{
|
|
||||||
_profileModule = profileModule ?? throw new ArgumentNullException(nameof(profileModule));
|
|
||||||
|
|
||||||
ThrowOnFail = true;
|
|
||||||
Accepts = MimeType.Json;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
|
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
|
||||||
{
|
{
|
||||||
@ -35,14 +25,6 @@ namespace Artemis.Core.Services
|
|||||||
Accepts = MimeType.Json;
|
Accepts = MimeType.Json;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DataModelJsonPluginEndPoint(DataModelExpansion<T> dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule)
|
|
||||||
{
|
|
||||||
_dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion));
|
|
||||||
|
|
||||||
ThrowOnFail = true;
|
|
||||||
Accepts = MimeType.Json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
|
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
|
||||||
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
|
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
|
||||||
@ -63,12 +45,7 @@ namespace Artemis.Core.Services
|
|||||||
using TextReader reader = context.OpenRequestText();
|
using TextReader reader = context.OpenRequestText();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_profileModule != null)
|
|
||||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _profileModule.DataModel);
|
|
||||||
else if (_module != null)
|
|
||||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
|
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
|
||||||
else
|
|
||||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel);
|
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -54,26 +54,6 @@ namespace Artemis.Core.Services
|
|||||||
/// <returns>The resulting end point</returns>
|
/// <returns>The resulting end point</returns>
|
||||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
|
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided
|
|
||||||
/// <paramref name="profileModule" />.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
|
||||||
/// <param name="profileModule">The module whose datamodel to apply the received JSON to</param>
|
|
||||||
/// <param name="endPointName">The name of the end point, must be unique</param>
|
|
||||||
/// <returns>The resulting end point</returns>
|
|
||||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided
|
|
||||||
/// <paramref name="dataModelExpansion" />.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
|
||||||
/// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
|
|
||||||
/// <param name="endPointName">The name of the end point, must be unique</param>
|
|
||||||
/// <returns>The resulting end point</returns>
|
|
||||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -142,24 +142,6 @@ namespace Artemis.Core.Services
|
|||||||
return endPoint;
|
return endPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel
|
|
||||||
{
|
|
||||||
if (profileModule == null) throw new ArgumentNullException(nameof(profileModule));
|
|
||||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
|
||||||
DataModelJsonPluginEndPoint<T> endPoint = new(profileModule, endPointName, PluginsModule);
|
|
||||||
PluginsModule.AddPluginEndPoint(endPoint);
|
|
||||||
return endPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel
|
|
||||||
{
|
|
||||||
if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion));
|
|
||||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
|
||||||
DataModelJsonPluginEndPoint<T> endPoint = new(dataModelExpansion, endPointName, PluginsModule);
|
|
||||||
PluginsModule.AddPluginEndPoint(endPoint);
|
|
||||||
return endPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel
|
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace Artemis.Core
|
|||||||
if (Registrations.Any(r => r.DataModel == dataModel))
|
if (Registrations.Any(r => r.DataModel == dataModel))
|
||||||
throw new ArtemisCoreException($"Data model store already contains data model '{dataModel.DataModelDescription}'");
|
throw new ArtemisCoreException($"Data model store already contains data model '{dataModel.DataModelDescription}'");
|
||||||
|
|
||||||
registration = new DataModelRegistration(dataModel, dataModel.Feature) {IsInStore = true};
|
registration = new DataModelRegistration(dataModel, dataModel.Module) {IsInStore = true};
|
||||||
Registrations.Add(registration);
|
Registrations.Add(registration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An empty data model plugin feature used by <see cref="Constants.CorePlugin" />
|
/// An empty data model plugin feature used by <see cref="Constants.CorePlugin" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class CorePluginFeature : DataModelPluginFeature
|
internal class CorePluginFeature : Module
|
||||||
{
|
{
|
||||||
public CorePluginFeature()
|
public CorePluginFeature()
|
||||||
{
|
{
|
||||||
@ -20,6 +21,10 @@ namespace Artemis.Core
|
|||||||
public override void Disable()
|
public override void Disable()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Update(double deltaTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class EffectPlaceholderPlugin : LayerEffectProvider
|
internal class EffectPlaceholderPlugin : LayerEffectProvider
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.Storage.Entities.Profile;
|
|
||||||
using Serilog;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class IntroAnimation
|
|
||||||
{
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IProfileService _profileService;
|
|
||||||
private readonly IEnumerable<ArtemisDevice> _devices;
|
|
||||||
|
|
||||||
public IntroAnimation(ILogger logger, IProfileService profileService, IEnumerable<ArtemisDevice> devices)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_profileService = profileService;
|
|
||||||
_devices = devices;
|
|
||||||
|
|
||||||
AnimationProfile = CreateIntroProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Profile AnimationProfile { get; set; }
|
|
||||||
|
|
||||||
public void Render(double deltaTime, SKCanvas canvas)
|
|
||||||
{
|
|
||||||
AnimationProfile.Update(deltaTime);
|
|
||||||
AnimationProfile.Render(canvas, SKPointI.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Profile CreateIntroProfile()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Load the intro profile from JSON into a ProfileEntity
|
|
||||||
string json = File.ReadAllText(Path.Combine(Constants.ApplicationFolder, "Resources", "intro-profile.json"));
|
|
||||||
ProfileEntity profileEntity = CoreJson.DeserializeObject<ProfileEntity>(json)!;
|
|
||||||
// Inject every LED on the surface into each layer
|
|
||||||
foreach (LayerEntity profileEntityLayer in profileEntity.Layers)
|
|
||||||
profileEntityLayer.Leds.AddRange(_devices.SelectMany(d => d.Leds).Select(l => new LedEntity
|
|
||||||
{
|
|
||||||
DeviceIdentifier = l.Device.Identifier,
|
|
||||||
LedName = l.RgbLed.Id.ToString()
|
|
||||||
}));
|
|
||||||
|
|
||||||
Profile profile = new(new DummyModule(), profileEntity);
|
|
||||||
profile.Activate(_devices);
|
|
||||||
|
|
||||||
_profileService.InstantiateProfile(profile);
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Warning(e, "Failed to load intro profile");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Profile(new DummyModule(), "Intro");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DummyModule : ProfileModule
|
|
||||||
{
|
|
||||||
public override void Enable()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Disable()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(double deltaTime)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ModuleActivated(bool isOverride)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ModuleDeactivated(bool isOverride)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Storage.Entities.Module
|
|
||||||
{
|
|
||||||
public class ModuleSettingsEntity
|
|
||||||
{
|
|
||||||
public ModuleSettingsEntity()
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public string ModuleId { get; set; }
|
|
||||||
public int PriorityCategory { get; set; }
|
|
||||||
public int Priority { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Entities.Profile
|
||||||
|
{
|
||||||
|
public class ProfileCategoryEntity
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool IsCollapsed { get; set; }
|
||||||
|
public bool IsSuspended { get; set; }
|
||||||
|
public int Order { get; set; }
|
||||||
|
|
||||||
|
public List<ProfileConfigurationEntity> ProfileConfigurations { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Entities.Profile
|
||||||
|
{
|
||||||
|
public class ProfileConfigurationEntity
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string MaterialIcon { get; set; }
|
||||||
|
public Guid FileIconId { get; set; }
|
||||||
|
public int IconType { get; set; }
|
||||||
|
|
||||||
|
public bool IsSuspended { get; set; }
|
||||||
|
public int ActivationBehaviour { get; set; }
|
||||||
|
public DataModelConditionGroupEntity ActivationCondition { get; set; }
|
||||||
|
|
||||||
|
public string ModuleId { get; set; }
|
||||||
|
|
||||||
|
public Guid ProfileCategoryId { get; set; }
|
||||||
|
public Guid ProfileId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,10 +13,8 @@ namespace Artemis.Storage.Entities.Profile
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string ModuleId { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool IsActive { get; set; }
|
|
||||||
public bool IsFreshImport { get; set; }
|
public bool IsFreshImport { get; set; }
|
||||||
|
|
||||||
public List<FolderEntity> Folders { get; set; }
|
public List<FolderEntity> Folders { get; set; }
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Artemis.Storage.Entities.Module;
|
|
||||||
using Artemis.Storage.Migrations.Interfaces;
|
using Artemis.Storage.Migrations.Interfaces;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@ -87,18 +86,6 @@ namespace Artemis.Storage.Migrations
|
|||||||
// Remove the default brush the user selected, this will make the UI pick a new one
|
// Remove the default brush the user selected, this will make the UI pick a new one
|
||||||
repository.Database.Execute("DELETE PluginSettingEntity WHERE $.Name = \"ProfileEditor.DefaultLayerBrushDescriptor\"");
|
repository.Database.Execute("DELETE PluginSettingEntity WHERE $.Name = \"ProfileEditor.DefaultLayerBrushDescriptor\"");
|
||||||
|
|
||||||
// Module settings
|
|
||||||
repository.Database.GetCollection<ModuleSettingsEntity>().DropIndex("PluginGuid");
|
|
||||||
ILiteCollection<BsonDocument> modules = repository.Database.GetCollection("ModuleSettingsEntity");
|
|
||||||
foreach (BsonDocument bsonDocument in modules.FindAll())
|
|
||||||
{
|
|
||||||
if (ReplaceIfFound(bsonDocument, "PluginGuid", "ModuleId", pluginMap))
|
|
||||||
modules.Update(bsonDocument);
|
|
||||||
else if (bsonDocument.ContainsKey("PluginGuid"))
|
|
||||||
modules.Delete(bsonDocument["_id"]);
|
|
||||||
}
|
|
||||||
repository.Database.GetCollection<ModuleSettingsEntity>().EnsureIndex(s => s.ModuleId, true);
|
|
||||||
|
|
||||||
// Profiles
|
// Profiles
|
||||||
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
|
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
|
||||||
foreach (BsonDocument bsonDocument in collection.FindAll())
|
foreach (BsonDocument bsonDocument in collection.FindAll())
|
||||||
|
|||||||
48
src/Artemis.Storage/Migrations/M0012ProfileCategories.cs
Normal file
48
src/Artemis.Storage/Migrations/M0012ProfileCategories.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.Storage.Migrations.Interfaces;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Migrations
|
||||||
|
{
|
||||||
|
public class M0012ProfileCategories : IStorageMigration
|
||||||
|
{
|
||||||
|
public int UserVersion => 12;
|
||||||
|
|
||||||
|
public void Apply(LiteRepository repository)
|
||||||
|
{
|
||||||
|
ILiteCollection<ProfileCategoryEntity> profileCategories = repository.Database.GetCollection<ProfileCategoryEntity>();
|
||||||
|
profileCategories.EnsureIndex(s => s.Name, true);
|
||||||
|
ProfileCategoryEntity? profileCategoryEntity = profileCategories.Find(c => c.Name == "Converted").FirstOrDefault();
|
||||||
|
if (profileCategoryEntity == null)
|
||||||
|
{
|
||||||
|
profileCategoryEntity = new ProfileCategoryEntity {Name = "Imported"};
|
||||||
|
profileCategories.Insert(profileCategoryEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
|
||||||
|
foreach (BsonDocument bsonDocument in collection.FindAll())
|
||||||
|
{
|
||||||
|
// Profiles with a ModuleId have not been converted
|
||||||
|
if (bsonDocument.ContainsKey("ModuleId"))
|
||||||
|
{
|
||||||
|
string moduleId = bsonDocument["ModuleId"].AsString;
|
||||||
|
bsonDocument.Remove("ModuleId");
|
||||||
|
|
||||||
|
ProfileConfigurationEntity profileConfiguration = new()
|
||||||
|
{
|
||||||
|
Name = bsonDocument["Name"].AsString,
|
||||||
|
MaterialIcon = "ApplicationImport",
|
||||||
|
ModuleId = moduleId,
|
||||||
|
ProfileId = bsonDocument["_id"].AsGuid
|
||||||
|
};
|
||||||
|
|
||||||
|
profileCategoryEntity.ProfileConfigurations.Add(profileConfiguration);
|
||||||
|
collection.Update(bsonDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profileCategories.Update(profileCategoryEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Artemis.Storage.Entities.Module;
|
|
||||||
|
|
||||||
namespace Artemis.Storage.Repositories.Interfaces
|
|
||||||
{
|
|
||||||
public interface IModuleRepository : IRepository
|
|
||||||
{
|
|
||||||
void Add(ModuleSettingsEntity moduleSettingsEntity);
|
|
||||||
ModuleSettingsEntity GetByModuleId(string moduleId);
|
|
||||||
List<ModuleSettingsEntity> GetAll();
|
|
||||||
List<ModuleSettingsEntity> GetByCategory(int category);
|
|
||||||
void Save(ModuleSettingsEntity moduleSettingsEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Repositories.Interfaces
|
||||||
|
{
|
||||||
|
public interface IProfileCategoryRepository : IRepository
|
||||||
|
{
|
||||||
|
void Add(ProfileCategoryEntity profileCategoryEntity);
|
||||||
|
void Remove(ProfileCategoryEntity profileCategoryEntity);
|
||||||
|
List<ProfileCategoryEntity> GetAll();
|
||||||
|
ProfileCategoryEntity Get(Guid id);
|
||||||
|
Stream GetProfileIconStream(Guid id);
|
||||||
|
void SaveProfileIconStream(ProfileConfigurationEntity profileConfigurationEntity, Stream stream);
|
||||||
|
ProfileCategoryEntity IsUnique(string name, Guid? id);
|
||||||
|
void Save(ProfileCategoryEntity profileCategoryEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,6 @@ namespace Artemis.Storage.Repositories.Interfaces
|
|||||||
void Remove(ProfileEntity profileEntity);
|
void Remove(ProfileEntity profileEntity);
|
||||||
List<ProfileEntity> GetAll();
|
List<ProfileEntity> GetAll();
|
||||||
ProfileEntity Get(Guid id);
|
ProfileEntity Get(Guid id);
|
||||||
List<ProfileEntity> GetByModuleId(string moduleId);
|
|
||||||
void Save(ProfileEntity profileEntity);
|
void Save(ProfileEntity profileEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,43 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Artemis.Storage.Entities.Module;
|
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
|
||||||
using LiteDB;
|
|
||||||
|
|
||||||
namespace Artemis.Storage.Repositories
|
|
||||||
{
|
|
||||||
internal class ModuleRepository : IModuleRepository
|
|
||||||
{
|
|
||||||
private readonly LiteRepository _repository;
|
|
||||||
|
|
||||||
public ModuleRepository(LiteRepository repository)
|
|
||||||
{
|
|
||||||
_repository = repository;
|
|
||||||
_repository.Database.GetCollection<ModuleSettingsEntity>().EnsureIndex(s => s.ModuleId, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(ModuleSettingsEntity moduleSettingsEntity)
|
|
||||||
{
|
|
||||||
_repository.Insert(moduleSettingsEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleSettingsEntity GetByModuleId(string moduleId)
|
|
||||||
{
|
|
||||||
return _repository.FirstOrDefault<ModuleSettingsEntity>(s => s.ModuleId == moduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ModuleSettingsEntity> GetAll()
|
|
||||||
{
|
|
||||||
return _repository.Query<ModuleSettingsEntity>().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ModuleSettingsEntity> GetByCategory(int category)
|
|
||||||
{
|
|
||||||
return _repository.Query<ModuleSettingsEntity>().Where(s => s.PriorityCategory == category).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save(ModuleSettingsEntity moduleSettingsEntity)
|
|
||||||
{
|
|
||||||
_repository.Upsert(moduleSettingsEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Repositories
|
||||||
|
{
|
||||||
|
internal class ProfileCategoryRepository : IProfileCategoryRepository
|
||||||
|
{
|
||||||
|
private readonly LiteRepository _repository;
|
||||||
|
private readonly ILiteStorage<Guid> _profileIcons;
|
||||||
|
|
||||||
|
public ProfileCategoryRepository(LiteRepository repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_repository.Database.GetCollection<ProfileCategoryEntity>().EnsureIndex(s => s.Name, true);
|
||||||
|
_profileIcons = _repository.Database.GetStorage<Guid>("profileIcons");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ProfileCategoryEntity profileCategoryEntity)
|
||||||
|
{
|
||||||
|
_repository.Insert(profileCategoryEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(ProfileCategoryEntity profileCategoryEntity)
|
||||||
|
{
|
||||||
|
_repository.Delete<ProfileCategoryEntity>(profileCategoryEntity.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ProfileCategoryEntity> GetAll()
|
||||||
|
{
|
||||||
|
return _repository.Query<ProfileCategoryEntity>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileCategoryEntity Get(Guid id)
|
||||||
|
{
|
||||||
|
return _repository.FirstOrDefault<ProfileCategoryEntity>(p => p.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileCategoryEntity IsUnique(string name, Guid? id)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
return _repository.FirstOrDefault<ProfileCategoryEntity>(p => p.Name == name);
|
||||||
|
return _repository.FirstOrDefault<ProfileCategoryEntity>(p => p.Name == name && p.Id != id.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(ProfileCategoryEntity profileCategoryEntity)
|
||||||
|
{
|
||||||
|
_repository.Upsert(profileCategoryEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream GetProfileIconStream(Guid id)
|
||||||
|
{
|
||||||
|
if (!_profileIcons.Exists(id))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
MemoryStream stream = new();
|
||||||
|
_profileIcons.Download(id, stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveProfileIconStream(ProfileConfigurationEntity profileConfigurationEntity, Stream stream)
|
||||||
|
{
|
||||||
|
if (profileConfigurationEntity.FileIconId == Guid.Empty)
|
||||||
|
profileConfigurationEntity.FileIconId = Guid.NewGuid();
|
||||||
|
|
||||||
|
if (stream == null && _profileIcons.Exists(profileConfigurationEntity.FileIconId))
|
||||||
|
_profileIcons.Delete(profileConfigurationEntity.FileIconId);
|
||||||
|
|
||||||
|
_profileIcons.Upload(profileConfigurationEntity.FileIconId, "image", stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,15 +36,6 @@ namespace Artemis.Storage.Repositories
|
|||||||
return _repository.FirstOrDefault<ProfileEntity>(p => p.Id == id);
|
return _repository.FirstOrDefault<ProfileEntity>(p => p.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ProfileEntity> GetByModuleId(string moduleId)
|
|
||||||
{
|
|
||||||
return _repository.Query<ProfileEntity>()
|
|
||||||
.Include(p => p.Folders)
|
|
||||||
.Include(p => p.Layers)
|
|
||||||
.Where(s => s.ModuleId == moduleId)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save(ProfileEntity profileEntity)
|
public void Save(ProfileEntity profileEntity)
|
||||||
{
|
{
|
||||||
_repository.Upsert(profileEntity);
|
_repository.Upsert(profileEntity);
|
||||||
|
|||||||
63
src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.xaml
Normal file
63
src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.xaml
Normal file
@ -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.Resources>
|
||||||
<ContextMenu.ItemsSource>
|
<ContextMenu.ItemsSource>
|
||||||
<CompositeCollection>
|
<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}}" />
|
<CollectionContainer Collection="{Binding Data.ExtraDataModelViewModels, Source={StaticResource DataContextProxy}}" />
|
||||||
<Separator Visibility="{Binding Data.HasExtraDataModels, Source={StaticResource DataContextProxy}, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"/>
|
<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}}" />
|
<CollectionContainer Collection="{Binding Data.DataModelViewModel.Children, Source={StaticResource DataContextProxy}}" />
|
||||||
|
|||||||
@ -19,8 +19,8 @@ namespace Artemis.UI.Shared.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataModelDynamicViewModel : PropertyChangedBase, IDisposable
|
public class DataModelDynamicViewModel : PropertyChangedBase, IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly List<Module> _modules;
|
||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
private readonly Module _module;
|
|
||||||
private readonly Timer _updateTimer;
|
private readonly Timer _updateTimer;
|
||||||
private SolidColorBrush _buttonBrush = new(Color.FromRgb(171, 71, 188));
|
private SolidColorBrush _buttonBrush = new(Color.FromRgb(171, 71, 188));
|
||||||
private DataModelPath? _dataModelPath;
|
private DataModelPath? _dataModelPath;
|
||||||
@ -31,9 +31,9 @@ namespace Artemis.UI.Shared.Input
|
|||||||
private bool _isEnabled = true;
|
private bool _isEnabled = true;
|
||||||
private string _placeholder = "Select a property";
|
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;
|
_dataModelUIService = dataModelUIService;
|
||||||
_updateTimer = new Timer(500);
|
_updateTimer = new Timer(500);
|
||||||
|
|
||||||
@ -107,6 +107,11 @@ namespace Artemis.UI.Shared.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public BindableCollection<DataModelPropertiesViewModel> ExtraDataModelViewModels { get; }
|
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>
|
/// <summary>
|
||||||
/// Gets a boolean indicating whether there are any extra data models
|
/// Gets a boolean indicating whether there are any extra data models
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -233,7 +238,7 @@ namespace Artemis.UI.Shared.Input
|
|||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
// Get the data models
|
// Get the data models
|
||||||
DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_module, true);
|
DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_modules, true);
|
||||||
if (DataModelViewModel != null)
|
if (DataModelViewModel != null)
|
||||||
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
|
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
|
||||||
ExtraDataModelViewModels.CollectionChanged += ExtraDataModelViewModelsOnCollectionChanged;
|
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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Artemis.Core.DataModelExpansions;
|
using Artemis.Core.DataModelExpansions;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Shared.Input;
|
using Artemis.UI.Shared.Input;
|
||||||
@ -20,9 +21,9 @@ namespace Artemis.UI.Shared
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="DataModelDynamicViewModel" /> class
|
/// Creates a new instance of the <see cref="DataModelDynamicViewModel" /> class
|
||||||
/// </summary>
|
/// </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>
|
/// <returns>A new instance of the <see cref="DataModelDynamicViewModel" /> class</returns>
|
||||||
DataModelDynamicViewModel DataModelDynamicViewModel(Module module);
|
DataModelDynamicViewModel DataModelDynamicViewModel(List<Module> modules);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="DataModelStaticViewModel" /> class
|
/// Creates a new instance of the <see cref="DataModelStaticViewModel" /> class
|
||||||
|
|||||||
@ -142,7 +142,7 @@ namespace Artemis.UI.Shared
|
|||||||
if (InputDragging)
|
if (InputDragging)
|
||||||
ProfileEditorService.UpdateProfilePreview();
|
ProfileEditorService.UpdateProfilePreview();
|
||||||
else
|
else
|
||||||
ProfileEditorService.UpdateSelectedProfileElement();
|
ProfileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ namespace Artemis.UI.Shared
|
|||||||
public void InputDragEnded(object sender, EventArgs e)
|
public void InputDragEnded(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
InputDragging = false;
|
InputDragging = false;
|
||||||
ProfileEditorService.UpdateSelectedProfileElement();
|
ProfileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LayerPropertyOnUpdated(object? sender, EventArgs e)
|
private void LayerPropertyOnUpdated(object? sender, EventArgs e)
|
||||||
|
|||||||
@ -35,7 +35,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
||||||
{
|
{
|
||||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
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)));
|
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
@ -47,7 +47,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
|
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
|
||||||
{
|
{
|
||||||
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children
|
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children
|
||||||
.Where(d => d.DataModel != null && !d.DataModel.Feature.IsEnabled)
|
.Where(d => d.DataModel != null && !d.DataModel.Module.IsEnabled)
|
||||||
.ToList();
|
.ToList();
|
||||||
foreach (DataModelVisualizationViewModel child in disabledChildren)
|
foreach (DataModelVisualizationViewModel child in disabledChildren)
|
||||||
mainDataModelVisualization.Children.Remove(child);
|
mainDataModelVisualization.Children.Remove(child);
|
||||||
@ -65,34 +65,33 @@ namespace Artemis.UI.Shared.Services
|
|||||||
mainDataModelVisualization.Update(this, null);
|
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)
|
if (includeMainDataModel)
|
||||||
|
root = GetMainDataModelVisualization();
|
||||||
|
else
|
||||||
{
|
{
|
||||||
DataModelPropertiesViewModel mainDataModel = GetMainDataModelVisualization();
|
root = new DataModelPropertiesViewModel(null, null, null);
|
||||||
|
root.UpdateRequested += (sender, args) => root.Update(this, null);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DataModel? dataModel = _dataModelService.GetPluginDataModel(pluginFeature);
|
foreach (Module module in modules)
|
||||||
|
{
|
||||||
|
DataModel? dataModel = _dataModelService.GetPluginDataModel(module);
|
||||||
if (dataModel == null)
|
if (dataModel == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
root.Children.Add(new DataModelPropertiesViewModel(dataModel, root, new DataModelPath(dataModel)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.Children.Any())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
|
||||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, new DataModelPath(dataModel)));
|
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
viewModel.Update(this, null);
|
root.Update(this, null);
|
||||||
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
|
return root;
|
||||||
return viewModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type>? compatibleConversionTypes = null) where T : DataModelInputViewModel
|
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)
|
public DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription)
|
||||||
|
|||||||
@ -34,10 +34,13 @@ namespace Artemis.UI.Shared.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a data model visualization view model for the data model of the provided plugin feature
|
/// Creates a data model visualization view model for the data model of the provided plugin feature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginFeature">The plugin feature to create hte data model visualization view model for</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</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Updates the children of the provided main data model visualization, removing disabled children and adding newly
|
/// 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>
|
/// <summary>
|
||||||
/// Creates a view model that allows selecting a value from the data model
|
/// Creates a view model that allows selecting a value from the data model
|
||||||
/// </summary>
|
/// </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>
|
/// <returns>A view model that allows selecting a value from the data model</returns>
|
||||||
DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module);
|
DataModelDynamicViewModel GetDynamicSelectionViewModel(List<Module> modules);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a view model that allows entering a value matching the target data model type
|
/// 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="confirmText">The text of the confirm button, defaults to "Confirm"</param>
|
||||||
/// <param name="cancelText">The text of the cancel button, defaults to "Cancel"</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Shows a confirm dialog on the dialog host provided in identifier.
|
/// 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="confirmText">The text of the confirm button, defaults to "Confirm"</param>
|
||||||
/// <param name="cancelText">The text of the cancel button, defaults to "Cancel"</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Shows a dialog by initializing a view model implementing <see cref="DialogViewModelBase" />
|
/// 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.Collections.ObjectModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services
|
namespace Artemis.UI.Shared.Services
|
||||||
{
|
{
|
||||||
@ -12,8 +11,15 @@ namespace Artemis.UI.Shared.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IProfileEditorService : IArtemisSharedUIService
|
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>
|
/// <summary>
|
||||||
/// Gets the currently selected profile
|
/// 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>
|
/// </summary>
|
||||||
Profile? SelectedProfile { get; }
|
Profile? SelectedProfile { get; }
|
||||||
|
|
||||||
@ -48,15 +54,15 @@ namespace Artemis.UI.Shared.Services
|
|||||||
bool Playing { get; set; }
|
bool Playing { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the selected profile
|
/// Changes the selected profile by its <see cref="ProfileConfiguration" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="profile">The profile to select</param>
|
/// <param name="profileConfiguration">The profile configuration of the profile to select</param>
|
||||||
void ChangeSelectedProfile(Profile? profile);
|
void ChangeSelectedProfileConfiguration(ProfileConfiguration? profileConfiguration);
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
void UpdateSelectedProfile();
|
void SaveSelectedProfileConfiguration();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the selected profile element
|
/// Changes the selected profile element
|
||||||
@ -65,9 +71,9 @@ namespace Artemis.UI.Shared.Services
|
|||||||
void ChangeSelectedProfileElement(RenderProfileElement? profileElement);
|
void ChangeSelectedProfileElement(RenderProfileElement? profileElement);
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
void UpdateSelectedProfileElement();
|
void SaveSelectedProfileElement();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the selected data binding property
|
/// Changes the selected data binding property
|
||||||
@ -81,22 +87,16 @@ namespace Artemis.UI.Shared.Services
|
|||||||
void UpdateProfilePreview();
|
void UpdateProfilePreview();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restores the profile to the last <see cref="UpdateSelectedProfile" /> call
|
/// Restores the profile to the last <see cref="SaveSelectedProfileConfiguration" /> call
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><see langword="true" /> if undo was successful, otherwise <see langword="false" /></returns>
|
/// <returns><see langword="true" /> if undo was successful, otherwise <see langword="false" /></returns>
|
||||||
bool UndoUpdateProfile();
|
bool UndoSaveProfile();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restores the profile to the last <see cref="UndoUpdateProfile" /> call
|
/// Restores the profile to the last <see cref="UndoSaveProfile" /> call
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><see langword="true" /> if redo was successful, otherwise <see langword="false" /></returns>
|
/// <returns><see langword="true" /> if redo was successful, otherwise <see langword="false" /></returns>
|
||||||
bool RedoUpdateProfile();
|
bool RedoSaveProfile();
|
||||||
|
|
||||||
/// <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();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
/// 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>
|
/// <summary>
|
||||||
/// Occurs when a new profile is selected
|
/// Occurs when a new profile is selected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<ProfileEventArgs> ProfileSelected;
|
event EventHandler<ProfileConfigurationEventArgs> SelectedProfileChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs then the currently selected profile is updated
|
/// Occurs then the currently selected profile is updated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<ProfileEventArgs> SelectedProfileUpdated;
|
event EventHandler<ProfileConfigurationEventArgs> SelectedProfileSaved;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a new profile element is selected
|
/// Occurs when a new profile element is selected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;
|
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the currently selected profile element is updated
|
/// Occurs when the currently selected profile element is updated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementUpdated;
|
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementSaved;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the currently selected data binding layer property is changed
|
/// 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 IProfileService _profileService;
|
||||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||||
private readonly IRgbService _rgbService;
|
private readonly IRgbService _rgbService;
|
||||||
|
private readonly IModuleService _moduleService;
|
||||||
private readonly object _selectedProfileElementLock = new();
|
private readonly object _selectedProfileElementLock = new();
|
||||||
private readonly object _selectedProfileLock = new();
|
private readonly object _selectedProfileLock = new();
|
||||||
private TimeSpan _currentTime;
|
private TimeSpan _currentTime;
|
||||||
private bool _doTick;
|
private bool _doTick;
|
||||||
private int _pixelsPerSecond;
|
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;
|
_kernel = kernel;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_rgbService = rgbService;
|
_rgbService = rgbService;
|
||||||
|
_moduleService = moduleService;
|
||||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||||
coreService.FrameRendered += CoreServiceOnFrameRendered;
|
coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||||
PixelsPerSecond = 100;
|
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)
|
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_doTick) return;
|
if (!_doTick) return;
|
||||||
@ -101,7 +56,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Trigger a profile change
|
// Trigger a profile change
|
||||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
|
OnSelectedProfileChanged(new ProfileConfigurationEventArgs(SelectedProfileConfiguration, SelectedProfileConfiguration));
|
||||||
// Trigger a selected element change
|
// Trigger a selected element change
|
||||||
RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement;
|
RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement;
|
||||||
if (SelectedProfileElement is Folder folder)
|
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 ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
|
||||||
|
|
||||||
public bool Playing { get; set; }
|
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 RenderProfileElement? SelectedProfileElement { get; private set; }
|
||||||
public ILayerProperty? SelectedDataBinding { 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)
|
lock (_selectedProfileLock)
|
||||||
{
|
{
|
||||||
if (SelectedProfile == profile)
|
if (SelectedProfileConfiguration == profileConfiguration)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (profile != null && !profile.IsActivated)
|
if (profileConfiguration?.Profile != null && profileConfiguration.Profile.Disposed)
|
||||||
throw new ArtemisSharedUIException("Cannot change the selected profile to an inactive profile");
|
throw new ArtemisSharedUIException("Cannot select a disposed profile");
|
||||||
|
|
||||||
_logger.Verbose("ChangeSelectedProfile {profile}", profile);
|
_logger.Verbose("ChangeSelectedProfileConfiguration {profile}", profileConfiguration);
|
||||||
ChangeSelectedProfileElement(null);
|
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
|
// The new profile may need activation
|
||||||
if (SelectedProfile != null)
|
SelectedProfileConfiguration = profileConfiguration;
|
||||||
SelectedProfile.Deactivated -= SelectedProfileOnDeactivated;
|
if (SelectedProfileConfiguration != null)
|
||||||
SelectedProfile = profile;
|
{
|
||||||
if (SelectedProfile != null)
|
SelectedProfileConfiguration.IsBeingEdited = true;
|
||||||
SelectedProfile.Deactivated += SelectedProfileOnDeactivated;
|
_moduleService.SetActivationOverride(SelectedProfileConfiguration.Module);
|
||||||
|
_profileService.ActivateProfile(SelectedProfileConfiguration);
|
||||||
|
_profileService.RenderForEditor = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_moduleService.SetActivationOverride(null);
|
||||||
|
_profileService.RenderForEditor = false;
|
||||||
|
}
|
||||||
|
|
||||||
OnSelectedProfileChanged(profileElementEvent);
|
OnSelectedProfileChanged(profileConfigurationElementEvent);
|
||||||
UpdateProfilePreview();
|
UpdateProfilePreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSelectedProfile()
|
public void SaveSelectedProfileConfiguration()
|
||||||
{
|
{
|
||||||
lock (_selectedProfileLock)
|
lock (_selectedProfileLock)
|
||||||
{
|
{
|
||||||
_logger.Verbose("UpdateSelectedProfile {profile}", SelectedProfile);
|
_logger.Verbose("SaveSelectedProfileConfiguration {profile}", SelectedProfile);
|
||||||
if (SelectedProfile == null)
|
if (SelectedProfile == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_profileService.UpdateProfile(SelectedProfile, true);
|
_profileService.SaveProfile(SelectedProfile, true);
|
||||||
OnSelectedProfileUpdated(new ProfileEventArgs(SelectedProfile));
|
OnSelectedProfileUpdated(new ProfileConfigurationEventArgs(SelectedProfileConfiguration));
|
||||||
UpdateProfilePreview();
|
UpdateProfilePreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,15 +201,15 @@ namespace Artemis.UI.Shared.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSelectedProfileElement()
|
public void SaveSelectedProfileElement()
|
||||||
{
|
{
|
||||||
lock (_selectedProfileElementLock)
|
lock (_selectedProfileElementLock)
|
||||||
{
|
{
|
||||||
_logger.Verbose("UpdateSelectedProfileElement {profile}", SelectedProfileElement);
|
_logger.Verbose("SaveSelectedProfileElement {profile}", SelectedProfileElement);
|
||||||
if (SelectedProfile == null)
|
if (SelectedProfile == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_profileService.UpdateProfile(SelectedProfile, true);
|
_profileService.SaveProfile(SelectedProfile, true);
|
||||||
OnSelectedProfileElementUpdated(new RenderProfileElementEventArgs(SelectedProfileElement));
|
OnSelectedProfileElementUpdated(new RenderProfileElementEventArgs(SelectedProfileElement));
|
||||||
UpdateProfilePreview();
|
UpdateProfilePreview();
|
||||||
}
|
}
|
||||||
@ -267,12 +228,12 @@ namespace Artemis.UI.Shared.Services
|
|||||||
Tick();
|
Tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UndoUpdateProfile()
|
public bool UndoSaveProfile()
|
||||||
{
|
{
|
||||||
if (SelectedProfile == null)
|
if (SelectedProfile == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool undid = _profileService.UndoUpdateProfile(SelectedProfile);
|
bool undid = _profileService.UndoSaveProfile(SelectedProfile);
|
||||||
if (!undid)
|
if (!undid)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -280,12 +241,12 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RedoUpdateProfile()
|
public bool RedoSaveProfile()
|
||||||
{
|
{
|
||||||
if (SelectedProfile == null)
|
if (SelectedProfile == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool redid = _profileService.RedoUpdateProfile(SelectedProfile);
|
bool redid = _profileService.RedoSaveProfile(SelectedProfile);
|
||||||
if (!redid)
|
if (!redid)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -319,8 +280,11 @@ namespace Artemis.UI.Shared.Services
|
|||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
if (existing.Plugin != plugin)
|
if (existing.Plugin != plugin)
|
||||||
|
{
|
||||||
throw new ArtemisSharedUIException($"Cannot register property editor for type {supportedType.Name} because an editor was already " +
|
throw new ArtemisSharedUIException($"Cannot register property editor for type {supportedType.Name} because an editor was already " +
|
||||||
$"registered by {existing.Plugin}");
|
$"registered by {existing.Plugin}");
|
||||||
|
}
|
||||||
|
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,8 +328,10 @@ namespace Artemis.UI.Shared.Services
|
|||||||
|
|
||||||
if (snapToCurrentTime)
|
if (snapToCurrentTime)
|
||||||
// Snap to the current time
|
// Snap to the current time
|
||||||
|
{
|
||||||
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
return CurrentTime;
|
return CurrentTime;
|
||||||
|
}
|
||||||
|
|
||||||
if (snapTimes != null)
|
if (snapTimes != null)
|
||||||
{
|
{
|
||||||
@ -401,13 +367,9 @@ namespace Artemis.UI.Shared.Services
|
|||||||
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
||||||
}
|
}
|
||||||
else if (registration != null)
|
else if (registration != null)
|
||||||
{
|
|
||||||
viewModelType = registration.ViewModelType;
|
viewModelType = registration.ViewModelType;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModelType == null)
|
if (viewModelType == null)
|
||||||
return null;
|
return null;
|
||||||
@ -419,11 +381,6 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
|
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProfileModule? GetCurrentModule()
|
|
||||||
{
|
|
||||||
return SelectedProfile?.Module;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ArtemisLed> GetLedsInRectangle(Rect rect)
|
public List<ArtemisLed> GetLedsInRectangle(Rect rect)
|
||||||
{
|
{
|
||||||
return _rgbService.EnabledDevices
|
return _rgbService.EnabledDevices
|
||||||
@ -432,15 +389,6 @@ namespace Artemis.UI.Shared.Services
|
|||||||
.ToList();
|
.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
|
#region Copy/paste
|
||||||
|
|
||||||
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
|
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
|
||||||
@ -512,7 +460,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
if (pasted != null)
|
if (pasted != null)
|
||||||
{
|
{
|
||||||
target.Profile.PopulateLeds(_rgbService.EnabledDevices);
|
target.Profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||||
UpdateSelectedProfile();
|
SaveSelectedProfileConfiguration();
|
||||||
ChangeSelectedProfileElement(pasted);
|
ChangeSelectedProfileElement(pasted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,5 +468,58 @@ namespace Artemis.UI.Shared.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
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...
|
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
|
||||||
public static readonly DependencyProperty DataProperty =
|
public static readonly DependencyProperty DataProperty =
|
||||||
|
|||||||
@ -369,11 +369,15 @@
|
|||||||
<Page Update="Screens\Plugins\PluginPrerequisitesUninstallDialogView.xaml">
|
<Page Update="Screens\Plugins\PluginPrerequisitesUninstallDialogView.xaml">
|
||||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
</Page>
|
</Page>
|
||||||
<Page Update="Screens\ProfileEditor\Dialogs\ProfileEditView.xaml">
|
|
||||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
|
||||||
</Page>
|
|
||||||
<Page Update="Screens\Settings\Debug\Tabs\Performance\PerformanceDebugView.xaml">
|
<Page Update="Screens\Settings\Debug\Tabs\Performance\PerformanceDebugView.xaml">
|
||||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Update="Screens\Sidebar\Dialogs\SidebarCategoryUpdateView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Screens\Sidebar\Models\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Converters
|
|
||||||
{
|
|
||||||
public class NullToVisibilityConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
Parameters direction;
|
|
||||||
if (parameter == null)
|
|
||||||
direction = Parameters.Normal;
|
|
||||||
else
|
|
||||||
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
|
|
||||||
|
|
||||||
if (direction == Parameters.Normal)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
return Visibility.Collapsed;
|
|
||||||
return Visibility.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
return Visibility.Visible;
|
|
||||||
return Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Parameters
|
|
||||||
{
|
|
||||||
Normal,
|
|
||||||
Inverted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
src/Artemis.UI/Converters/SolidColorBrushToColorConverter.cs
Normal file
29
src/Artemis.UI/Converters/SolidColorBrushToColorConverter.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Converters
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Converts <see cref="T:SolidColorBrush" /> into <see cref="T:System.Windows.Media.SolidColorBrush" />.
|
||||||
|
/// </summary>
|
||||||
|
[ValueConversion(typeof(SolidColorBrush), typeof(Color))]
|
||||||
|
public class SolidColorBrushToColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is SolidColorBrush brush)
|
||||||
|
return brush.Color;
|
||||||
|
return Colors.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return Binding.DoNothing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,11 +2,11 @@
|
|||||||
{
|
{
|
||||||
public class RequestSelectSidebarItemEvent
|
public class RequestSelectSidebarItemEvent
|
||||||
{
|
{
|
||||||
public RequestSelectSidebarItemEvent(string label)
|
public RequestSelectSidebarItemEvent(string displayName)
|
||||||
{
|
{
|
||||||
Label = label;
|
DisplayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Label { get; }
|
public string DisplayName { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,9 +1,7 @@
|
|||||||
using Artemis.Core;
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Screens.Modules;
|
|
||||||
using Artemis.UI.Screens.Modules.Tabs;
|
|
||||||
using Artemis.UI.Screens.Plugins;
|
using Artemis.UI.Screens.Plugins;
|
||||||
using Artemis.UI.Screens.ProfileEditor;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions;
|
using Artemis.UI.Screens.ProfileEditor.Conditions;
|
||||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties;
|
using Artemis.UI.Screens.ProfileEditor.LayerProperties;
|
||||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings;
|
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings;
|
||||||
@ -17,12 +15,13 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
|
|||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Visualization;
|
using Artemis.UI.Screens.ProfileEditor.Visualization;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Visualization.Tools;
|
using Artemis.UI.Screens.ProfileEditor.Visualization.Tools;
|
||||||
using Artemis.UI.Screens.Settings.Debug;
|
|
||||||
using Artemis.UI.Screens.Settings.Device;
|
using Artemis.UI.Screens.Settings.Device;
|
||||||
using Artemis.UI.Screens.Settings.Device.Tabs;
|
using Artemis.UI.Screens.Settings.Device.Tabs;
|
||||||
using Artemis.UI.Screens.Settings.Tabs.Devices;
|
using Artemis.UI.Screens.Settings.Tabs.Devices;
|
||||||
using Artemis.UI.Screens.Settings.Tabs.Plugins;
|
using Artemis.UI.Screens.Settings.Tabs.Plugins;
|
||||||
using Artemis.UI.Screens.Shared;
|
using Artemis.UI.Screens.Shared;
|
||||||
|
using Artemis.UI.Screens.Sidebar;
|
||||||
|
using Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject.Factories
|
namespace Artemis.UI.Ninject.Factories
|
||||||
@ -31,14 +30,6 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IModuleVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
ModuleRootViewModel CreateModuleRootViewModel(Module module);
|
|
||||||
ProfileEditorViewModel CreateProfileEditorViewModel(ProfileModule module);
|
|
||||||
ActivationRequirementsViewModel CreateActivationRequirementsViewModel(Module module);
|
|
||||||
ActivationRequirementViewModel CreateActivationRequirementViewModel(IModuleActivationRequirement activationRequirement);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ISettingsVmFactory : IVmFactory
|
public interface ISettingsVmFactory : IVmFactory
|
||||||
{
|
{
|
||||||
PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin);
|
PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin);
|
||||||
@ -84,12 +75,12 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
|
|
||||||
public interface IDataModelConditionsVmFactory : IVmFactory
|
public interface IDataModelConditionsVmFactory : IVmFactory
|
||||||
{
|
{
|
||||||
DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, ConditionGroupType groupType);
|
DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, ConditionGroupType groupType, List<Module> modules);
|
||||||
DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList);
|
DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList, List<Module> modules);
|
||||||
DataModelConditionEventViewModel DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent);
|
DataModelConditionEventViewModel DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent, List<Module> modules);
|
||||||
DataModelConditionGeneralPredicateViewModel DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate);
|
DataModelConditionGeneralPredicateViewModel DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate, List<Module> modules);
|
||||||
DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate);
|
DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate, List<Module> modules);
|
||||||
DataModelConditionEventPredicateViewModel DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate);
|
DataModelConditionEventPredicateViewModel DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate, List<Module> modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ILayerPropertyVmFactory : IVmFactory
|
public interface ILayerPropertyVmFactory : IVmFactory
|
||||||
@ -111,6 +102,13 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
|
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ISidebarVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
|
||||||
|
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
|
||||||
|
ModuleActivationRequirementViewModel ModuleActivationRequirementViewModel(IModuleActivationRequirement activationRequirement);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Move these two
|
// TODO: Move these two
|
||||||
public interface IDataBindingsVmFactory
|
public interface IDataBindingsVmFactory
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.Modules.ModuleRootView"
|
|
||||||
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:s="https://github.com/canton7/Stylet"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:modules="clr-namespace:Artemis.UI.Screens.Modules"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800"
|
|
||||||
d:DataContext="{d:DesignInstance modules:ModuleRootViewModel}">
|
|
||||||
|
|
||||||
<TabControl Margin="0 -1 0 0"
|
|
||||||
ItemsSource="{Binding Items}"
|
|
||||||
SelectedItem="{Binding ActiveItem}"
|
|
||||||
DisplayMemberPath="DisplayName"
|
|
||||||
Style="{StaticResource MaterialDesignTabControl}">
|
|
||||||
<TabControl.ContentTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<materialDesign:TransitioningContent OpeningEffect="{materialDesign:TransitionEffect FadeIn}">
|
|
||||||
<ContentControl s:View.Model="{Binding IsAsync=True}" TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
|
|
||||||
</materialDesign:TransitioningContent>
|
|
||||||
</DataTemplate>
|
|
||||||
</TabControl.ContentTemplate>
|
|
||||||
</TabControl>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.UI.Ninject.Factories;
|
|
||||||
using Artemis.UI.Shared.Modules;
|
|
||||||
using Ninject;
|
|
||||||
using Ninject.Parameters;
|
|
||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Modules
|
|
||||||
{
|
|
||||||
public class ModuleRootViewModel : Conductor<Screen>.Collection.OneActive
|
|
||||||
{
|
|
||||||
private readonly IModuleVmFactory _moduleVmFactory;
|
|
||||||
|
|
||||||
public ModuleRootViewModel(Module module, IModuleVmFactory moduleVmFactory)
|
|
||||||
{
|
|
||||||
DisplayName = module?.DisplayName;
|
|
||||||
Module = module;
|
|
||||||
|
|
||||||
_moduleVmFactory = moduleVmFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Module Module { get; }
|
|
||||||
|
|
||||||
protected override void OnInitialActivate()
|
|
||||||
{
|
|
||||||
AddTabs();
|
|
||||||
base.OnInitialActivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddTabs()
|
|
||||||
{
|
|
||||||
// Create the profile editor and module VMs
|
|
||||||
if (Module is ProfileModule profileModule)
|
|
||||||
Items.Add(_moduleVmFactory.CreateProfileEditorViewModel(profileModule));
|
|
||||||
|
|
||||||
if (Module.ActivationRequirements.Any())
|
|
||||||
Items.Add(_moduleVmFactory.CreateActivationRequirementsViewModel(Module));
|
|
||||||
|
|
||||||
if (Module.ModuleTabs != null)
|
|
||||||
{
|
|
||||||
List<ModuleTab> moduleTabs = new(Module.ModuleTabs);
|
|
||||||
foreach (ModuleTab moduleTab in moduleTabs.Where(m => m != null))
|
|
||||||
{
|
|
||||||
ConstructorArgument module = new("module", Module);
|
|
||||||
ConstructorArgument displayName = new("displayName", DisplayName);
|
|
||||||
|
|
||||||
ModuleViewModel viewModel = (ModuleViewModel) Module.Plugin.Kernel.Get(moduleTab.Type, module, displayName);
|
|
||||||
Items.Add(viewModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveItem = Items.FirstOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.Modules.Tabs.ActivationRequirementView"
|
|
||||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Modules.Tabs"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800"
|
|
||||||
d:DataContext="{d:DesignInstance tabs:ActivationRequirementViewModel}">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<StackPanel Grid.Column="0">
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
|
|
||||||
Text="{Binding RequirementName}" />
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
|
|
||||||
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Text="{Binding RequirementDescription}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
|
||||||
<ToggleButton Style="{StaticResource MaterialDesignActionToggleButton}"
|
|
||||||
Focusable="False"
|
|
||||||
IsHitTestVisible="False"
|
|
||||||
IsChecked="{Binding RequirementMet}">
|
|
||||||
<ToggleButton.Content>
|
|
||||||
<Border Background="#E74C4C" Width="32" Height="32">
|
|
||||||
<materialDesign:PackIcon Kind="Close" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
|
||||||
</Border>
|
|
||||||
</ToggleButton.Content>
|
|
||||||
<materialDesign:ToggleButtonAssist.OnContent>
|
|
||||||
<materialDesign:PackIcon Kind="Check" />
|
|
||||||
</materialDesign:ToggleButtonAssist.OnContent>
|
|
||||||
</ToggleButton>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.Modules.Tabs.ActivationRequirementsView"
|
|
||||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Modules.Tabs"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800"
|
|
||||||
d:DataContext="{d:DesignInstance local:ActivationRequirementsViewModel}">
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
|
||||||
<StackPanel Margin="15" MaxWidth="800">
|
|
||||||
<!-- General settings -->
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">Activation requirements</TextBlock>
|
|
||||||
<TextBlock Margin="0 0 0 15" TextWrapping="Wrap" Style="{StaticResource MaterialDesignTextBlock}">
|
|
||||||
This module has built-in activation requirements and won't activate until
|
|
||||||
<Run Text="{Binding ActivationType}" FontWeight="Medium" Foreground="{StaticResource SecondaryHueMidBrush}" />. <LineBreak />
|
|
||||||
These requirements allow the module creator to decide when the module is activated and you cannot override them.
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
|
|
||||||
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
|
|
||||||
TextWrapping="Wrap">
|
|
||||||
Note: While you have the profile editor open the module is always activated and any other modules are deactivated.
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{Binding Items}">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel>
|
|
||||||
<Separator Margin="0 -10">
|
|
||||||
<Separator.Style>
|
|
||||||
<Style TargetType="Separator" BasedOn="{StaticResource MaterialDesignSeparator}">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Separator.Style>
|
|
||||||
</Separator>
|
|
||||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" Margin="20" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
|
|
||||||
</materialDesign:Card>
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.UI.Ninject.Factories;
|
|
||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Modules.Tabs
|
|
||||||
{
|
|
||||||
public class ActivationRequirementsViewModel : Conductor<ActivationRequirementViewModel>.Collection.AllActive
|
|
||||||
{
|
|
||||||
private readonly IModuleVmFactory _moduleVmFactory;
|
|
||||||
|
|
||||||
public ActivationRequirementsViewModel(Module module, IModuleVmFactory moduleVmFactory)
|
|
||||||
{
|
|
||||||
_moduleVmFactory = moduleVmFactory;
|
|
||||||
|
|
||||||
DisplayName = "ACTIVATION REQUIREMENTS";
|
|
||||||
Module = module;
|
|
||||||
|
|
||||||
ActivationType = Module.ActivationRequirementMode == ActivationRequirementType.All
|
|
||||||
? "all requirements are met"
|
|
||||||
: "any requirement is met";
|
|
||||||
}
|
|
||||||
|
|
||||||
public Module Module { get; }
|
|
||||||
|
|
||||||
public string ActivationType { get; set; }
|
|
||||||
|
|
||||||
protected override void OnActivate()
|
|
||||||
{
|
|
||||||
Items.Clear();
|
|
||||||
Items.AddRange(Module.ActivationRequirements.Select(_moduleVmFactory.CreateActivationRequirementViewModel));
|
|
||||||
|
|
||||||
base.OnActivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.News.NewsView"
|
|
||||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top" Margin="16">
|
|
||||||
<materialDesign:PackIcon Kind="Crane" Width="250" Height="250" HorizontalAlignment="Center" />
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 25">
|
|
||||||
News is not yet implemented
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 25">
|
|
||||||
The news page will keep you up-to-date with the latest developments in the Artemis community. <LineBreak />
|
|
||||||
You'll find the latest patch notes here and see featured workshop contributions.<LineBreak /><LineBreak />
|
|
||||||
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.News
|
|
||||||
{
|
|
||||||
public class NewsViewModel : Screen, IMainScreenViewModel
|
|
||||||
{
|
|
||||||
public NewsViewModel()
|
|
||||||
{
|
|
||||||
DisplayName = "News";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Input;
|
using Artemis.UI.Shared.Input;
|
||||||
@ -16,6 +17,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
{
|
{
|
||||||
private readonly IConditionOperatorService _conditionOperatorService;
|
private readonly IConditionOperatorService _conditionOperatorService;
|
||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
private readonly List<Module> _modules;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private DataModelStaticViewModel _rightSideInputViewModel;
|
private DataModelStaticViewModel _rightSideInputViewModel;
|
||||||
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
||||||
@ -25,11 +27,13 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
|
|
||||||
protected DataModelConditionPredicateViewModel(
|
protected DataModelConditionPredicateViewModel(
|
||||||
DataModelConditionPredicate dataModelConditionPredicate,
|
DataModelConditionPredicate dataModelConditionPredicate,
|
||||||
|
List<Module> modules,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelUIService dataModelUIService,
|
IDataModelUIService dataModelUIService,
|
||||||
IConditionOperatorService conditionOperatorService,
|
IConditionOperatorService conditionOperatorService,
|
||||||
ISettingsService settingsService) : base(dataModelConditionPredicate)
|
ISettingsService settingsService) : base(dataModelConditionPredicate)
|
||||||
{
|
{
|
||||||
|
_modules = modules;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
_conditionOperatorService = conditionOperatorService;
|
_conditionOperatorService = conditionOperatorService;
|
||||||
@ -44,6 +48,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model;
|
public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model;
|
||||||
public PluginSetting<bool> ShowDataModelValues { get; }
|
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||||
|
|
||||||
|
public bool CanSelectOperator => DataModelConditionPredicate.LeftPath is {IsValid: true};
|
||||||
|
|
||||||
public BaseConditionOperator SelectedOperator
|
public BaseConditionOperator SelectedOperator
|
||||||
{
|
{
|
||||||
get => _selectedOperator;
|
get => _selectedOperator;
|
||||||
@ -75,12 +81,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
public override void Delete()
|
public override void Delete()
|
||||||
{
|
{
|
||||||
base.Delete();
|
base.Delete();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Initialize()
|
public virtual void Initialize()
|
||||||
{
|
{
|
||||||
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules);
|
||||||
LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected;
|
LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected;
|
||||||
if (LeftSideColor != null)
|
if (LeftSideColor != null)
|
||||||
LeftSideSelectionViewModel.ButtonBrush = LeftSideColor;
|
LeftSideSelectionViewModel.ButtonBrush = LeftSideColor;
|
||||||
@ -107,10 +113,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
else if (!Operators.Contains(DataModelConditionPredicate.Operator))
|
else if (!Operators.Contains(DataModelConditionPredicate.Operator))
|
||||||
DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.Description == DataModelConditionPredicate.Operator.Description) ?? Operators.FirstOrDefault());
|
DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.Description == DataModelConditionPredicate.Operator.Description) ?? Operators.FirstOrDefault());
|
||||||
|
|
||||||
|
NotifyOfPropertyChange(nameof(CanSelectOperator));
|
||||||
SelectedOperator = DataModelConditionPredicate.Operator;
|
SelectedOperator = DataModelConditionPredicate.Operator;
|
||||||
|
|
||||||
// Without a selected operator or one that supports a right side, leave the right side input empty
|
// Without a selected operator or one that supports a right side, leave the right side input empty
|
||||||
if (SelectedOperator == null || SelectedOperator.RightSideType == null)
|
if (SelectedOperator?.RightSideType == null)
|
||||||
{
|
{
|
||||||
DisposeRightSideStaticViewModel();
|
DisposeRightSideStaticViewModel();
|
||||||
DisposeRightSideDynamicViewModel();
|
DisposeRightSideDynamicViewModel();
|
||||||
@ -149,7 +156,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
|
DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
|
||||||
SelectedOperator = DataModelConditionPredicate.Operator;
|
SelectedOperator = DataModelConditionPredicate.Operator;
|
||||||
Update();
|
Update();
|
||||||
@ -158,7 +165,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
public void ApplyRightSideDynamic()
|
public void ApplyRightSideDynamic()
|
||||||
{
|
{
|
||||||
DataModelConditionPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath);
|
DataModelConditionPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
@ -166,7 +173,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
public void ApplyRightSideStatic(object value)
|
public void ApplyRightSideStatic(object value)
|
||||||
{
|
{
|
||||||
DataModelConditionPredicate.UpdateRightSideStatic(value);
|
DataModelConditionPredicate.UpdateRightSideStatic(value);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
@ -174,7 +181,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
public void ApplyOperator()
|
public void ApplyOperator()
|
||||||
{
|
{
|
||||||
DataModelConditionPredicate.UpdateOperator(SelectedOperator);
|
DataModelConditionPredicate.UpdateOperator(SelectedOperator);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
@ -196,6 +203,26 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
ApplyOperator();
|
ApplyOperator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdateModules()
|
||||||
|
{
|
||||||
|
if (LeftSideSelectionViewModel != null)
|
||||||
|
{
|
||||||
|
LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected;
|
||||||
|
LeftSideSelectionViewModel.Dispose();
|
||||||
|
LeftSideSelectionViewModel = null;
|
||||||
|
}
|
||||||
|
DisposeRightSideStaticViewModel();
|
||||||
|
DisposeRightSideDynamicViewModel();
|
||||||
|
|
||||||
|
// If the modules changed the paths may no longer be valid if they targeted a module no longer available, in that case clear the path
|
||||||
|
if (DataModelConditionPredicate.LeftPath?.Target != null && !DataModelConditionPredicate.LeftPath.Target.IsExpansion && !_modules.Contains(DataModelConditionPredicate.LeftPath.Target.Module))
|
||||||
|
DataModelConditionPredicate.UpdateLeftSide(null);
|
||||||
|
if (DataModelConditionPredicate.RightPath?.Target != null && !DataModelConditionPredicate.RightPath.Target.IsExpansion && !_modules.Contains(DataModelConditionPredicate.RightPath.Target.Module))
|
||||||
|
DataModelConditionPredicate.UpdateRightSideDynamic(null);
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
@ -227,7 +254,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
|
|
||||||
private void CreateRightSideSelectionViewModel()
|
private void CreateRightSideSelectionViewModel()
|
||||||
{
|
{
|
||||||
RightSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
RightSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules);
|
||||||
RightSideSelectionViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush");
|
RightSideSelectionViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush");
|
||||||
RightSideSelectionViewModel.DisplaySwitchButton = true;
|
RightSideSelectionViewModel.DisplaySwitchButton = true;
|
||||||
RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
|
RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
|
||||||
|
|||||||
@ -63,5 +63,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
groupViewModel.ConvertToPredicate(this);
|
groupViewModel.ConvertToPredicate(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void UpdateModules();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
@ -14,15 +15,18 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
{
|
{
|
||||||
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
private readonly List<Module> _modules;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private DateTime _lastTrigger;
|
private DateTime _lastTrigger;
|
||||||
private string _triggerPastParticiple;
|
private string _triggerPastParticiple;
|
||||||
|
|
||||||
public DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent,
|
public DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent,
|
||||||
|
List<Module> modules,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelUIService dataModelUIService,
|
IDataModelUIService dataModelUIService,
|
||||||
IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionEvent)
|
IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionEvent)
|
||||||
{
|
{
|
||||||
|
_modules = modules;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
||||||
@ -46,7 +50,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules);
|
||||||
LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
|
LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
|
||||||
LeftSideSelectionViewModel.LoadEventChildren = false;
|
LeftSideSelectionViewModel.LoadEventChildren = false;
|
||||||
|
|
||||||
@ -82,7 +86,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
|
if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.Event);
|
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.Event, _modules);
|
||||||
viewModel.IsRootGroup = true;
|
viewModel.IsRootGroup = true;
|
||||||
viewModels.Add(viewModel);
|
viewModels.Add(viewModel);
|
||||||
}
|
}
|
||||||
@ -103,11 +107,18 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
public void ApplyEvent()
|
public void ApplyEvent()
|
||||||
{
|
{
|
||||||
DataModelConditionEvent.UpdateEvent(LeftSideSelectionViewModel.DataModelPath);
|
DataModelConditionEvent.UpdateEvent(LeftSideSelectionViewModel.DataModelPath);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdateModules()
|
||||||
|
{
|
||||||
|
LeftSideSelectionViewModel.Dispose();
|
||||||
|
LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected;
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnInitialActivate()
|
protected override void OnInitialActivate()
|
||||||
{
|
{
|
||||||
Initialize();
|
Initialize();
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Extensions;
|
using Artemis.UI.Extensions;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
@ -14,17 +16,23 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
public class DataModelConditionGroupViewModel : DataModelConditionViewModel
|
public class DataModelConditionGroupViewModel : DataModelConditionViewModel
|
||||||
{
|
{
|
||||||
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
||||||
|
private readonly List<Module> _modules;
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private bool _isEventGroup;
|
private bool _isEventGroup;
|
||||||
private bool _isRootGroup;
|
private bool _isRootGroup;
|
||||||
|
|
||||||
public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup,
|
public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup,
|
||||||
ConditionGroupType groupType,
|
ConditionGroupType groupType,
|
||||||
|
List<Module> modules,
|
||||||
|
ICoreService coreService,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelConditionsVmFactory dataModelConditionsVmFactory)
|
IDataModelConditionsVmFactory dataModelConditionsVmFactory)
|
||||||
: base(dataModelConditionGroup)
|
: base(dataModelConditionGroup)
|
||||||
{
|
{
|
||||||
GroupType = groupType;
|
GroupType = groupType;
|
||||||
|
_modules = modules;
|
||||||
|
_coreService = coreService;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
||||||
|
|
||||||
@ -66,7 +74,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
DataModelConditionGroup.BooleanOperator = enumValue;
|
DataModelConditionGroup.BooleanOperator = enumValue;
|
||||||
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
|
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
|
||||||
|
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddCondition()
|
public void AddCondition()
|
||||||
@ -87,7 +95,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
}
|
}
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddEventCondition()
|
public void AddEventCondition()
|
||||||
@ -104,7 +112,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
DataModelConditionGroup.AddChild(new DataModelConditionEvent(DataModelConditionGroup), index);
|
DataModelConditionGroup.AddChild(new DataModelConditionEvent(DataModelConditionGroup), index);
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddGroup()
|
public void AddGroup()
|
||||||
@ -112,7 +120,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
DataModelConditionGroup.AddChild(new DataModelConditionGroup(DataModelConditionGroup));
|
DataModelConditionGroup.AddChild(new DataModelConditionGroup(DataModelConditionGroup));
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
@ -131,22 +139,22 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
switch (childModel)
|
switch (childModel)
|
||||||
{
|
{
|
||||||
case DataModelConditionGroup dataModelConditionGroup:
|
case DataModelConditionGroup dataModelConditionGroup:
|
||||||
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, GroupType));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, GroupType, _modules));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionList dataModelConditionList:
|
case DataModelConditionList dataModelConditionList:
|
||||||
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(dataModelConditionList));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(dataModelConditionList, _modules));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionEvent dataModelConditionEvent:
|
case DataModelConditionEvent dataModelConditionEvent:
|
||||||
Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventViewModel(dataModelConditionEvent));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventViewModel(dataModelConditionEvent, _modules));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate:
|
case DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate:
|
||||||
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGeneralPredicateViewModel(dataModelConditionGeneralPredicate));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGeneralPredicateViewModel(dataModelConditionGeneralPredicate, _modules));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionListPredicate dataModelConditionListPredicate:
|
case DataModelConditionListPredicate dataModelConditionListPredicate:
|
||||||
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(dataModelConditionListPredicate));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(dataModelConditionListPredicate, _modules));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionEventPredicate dataModelConditionEventPredicate:
|
case DataModelConditionEventPredicate dataModelConditionEventPredicate:
|
||||||
Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventPredicateViewModel(dataModelConditionEventPredicate));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventPredicateViewModel(dataModelConditionEventPredicate, _modules));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,6 +181,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
dataModelConditionViewModel.Evaluate();
|
dataModelConditionViewModel.Evaluate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdateModules()
|
||||||
|
{
|
||||||
|
foreach (DataModelConditionViewModel dataModelConditionViewModel in Items)
|
||||||
|
dataModelConditionViewModel.UpdateModules();
|
||||||
|
}
|
||||||
|
|
||||||
public void ConvertToConditionList(DataModelConditionViewModel predicateViewModel)
|
public void ConvertToConditionList(DataModelConditionViewModel predicateViewModel)
|
||||||
{
|
{
|
||||||
// Store the old index and remove the old predicate
|
// Store the old index and remove the old predicate
|
||||||
@ -203,8 +217,33 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
|
||||||
|
{
|
||||||
|
if (IsRootGroup)
|
||||||
|
Evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
public event EventHandler Updated;
|
public event EventHandler Updated;
|
||||||
|
|
||||||
|
#region Overrides of Screen
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialActivate()
|
||||||
|
{
|
||||||
|
base.OnInitialActivate();
|
||||||
|
Update();
|
||||||
|
_coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnClose()
|
||||||
|
{
|
||||||
|
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
|
||||||
|
base.OnClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected virtual void OnUpdated()
|
protected virtual void OnUpdated()
|
||||||
{
|
{
|
||||||
Updated?.Invoke(this, EventArgs.Empty);
|
Updated?.Invoke(this, EventArgs.Empty);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms.VisualStyles;
|
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
@ -16,14 +16,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
{
|
{
|
||||||
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
private readonly List<Module> _modules;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
public DataModelConditionListViewModel(
|
public DataModelConditionListViewModel(DataModelConditionList dataModelConditionList,
|
||||||
DataModelConditionList dataModelConditionList,
|
List<Module> modules,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelUIService dataModelUIService,
|
IDataModelUIService dataModelUIService,
|
||||||
IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionList)
|
IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionList)
|
||||||
{
|
{
|
||||||
|
_modules = modules;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
||||||
@ -39,7 +41,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
DataModelConditionList.ListOperator = enumValue;
|
DataModelConditionList.ListOperator = enumValue;
|
||||||
NotifyOfPropertyChange(nameof(SelectedListOperator));
|
NotifyOfPropertyChange(nameof(SelectedListOperator));
|
||||||
|
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddCondition()
|
public void AddCondition()
|
||||||
@ -47,7 +49,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
DataModelConditionList.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionList, ProfileRightSideType.Dynamic));
|
DataModelConditionList.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionList, ProfileRightSideType.Dynamic));
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddGroup()
|
public void AddGroup()
|
||||||
@ -55,7 +57,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
DataModelConditionList.AddChild(new DataModelConditionGroup(DataModelConditionList));
|
DataModelConditionList.AddChild(new DataModelConditionGroup(DataModelConditionList));
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Evaluate()
|
public override void Evaluate()
|
||||||
@ -68,12 +70,19 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
public override void Delete()
|
public override void Delete()
|
||||||
{
|
{
|
||||||
base.Delete();
|
base.Delete();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void UpdateModules()
|
||||||
|
{
|
||||||
|
foreach (DataModelConditionViewModel dataModelConditionViewModel in Items)
|
||||||
|
dataModelConditionViewModel.UpdateModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules);
|
||||||
LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
|
LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
|
||||||
|
|
||||||
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
||||||
@ -96,7 +105,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
DataModelConditionList.UpdateList(LeftSideSelectionViewModel.DataModelPath);
|
DataModelConditionList.UpdateList(LeftSideSelectionViewModel.DataModelPath);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.SaveSelectedProfileElement();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
@ -120,7 +129,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
|
if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.List);
|
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.List, _modules);
|
||||||
viewModel.IsRootGroup = true;
|
viewModel.IsRootGroup = true;
|
||||||
viewModels.Add(viewModel);
|
viewModels.Add(viewModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
Background="#7B7B7B"
|
Background="#7B7B7B"
|
||||||
BorderBrush="#7B7B7B"
|
BorderBrush="#7B7B7B"
|
||||||
Content="{Binding SelectedOperator.Description}"
|
Content="{Binding SelectedOperator.Description}"
|
||||||
|
IsEnabled="{Binding CanSelectOperator}"
|
||||||
Click="PropertyButton_OnClick">
|
Click="PropertyButton_OnClick">
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu ItemsSource="{Binding Operators}">
|
<ContextMenu ItemsSource="{Binding Operators}">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Extensions;
|
using Artemis.UI.Extensions;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
@ -16,11 +17,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
|
||||||
public DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate,
|
public DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate,
|
||||||
|
List<Module> modules,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelUIService dataModelUIService,
|
IDataModelUIService dataModelUIService,
|
||||||
IConditionOperatorService conditionOperatorService,
|
IConditionOperatorService conditionOperatorService,
|
||||||
ISettingsService settingsService)
|
ISettingsService settingsService)
|
||||||
: base(dataModelConditionEventPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
: base(dataModelConditionEventPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
||||||
{
|
{
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
Background="#7B7B7B"
|
Background="#7B7B7B"
|
||||||
BorderBrush="#7B7B7B"
|
BorderBrush="#7B7B7B"
|
||||||
Content="{Binding SelectedOperator.Description}"
|
Content="{Binding SelectedOperator.Description}"
|
||||||
|
IsEnabled="{Binding CanSelectOperator}"
|
||||||
Click="PropertyButton_OnClick">
|
Click="PropertyButton_OnClick">
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu ItemsSource="{Binding Operators}">
|
<ContextMenu ItemsSource="{Binding Operators}">
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
@ -14,11 +15,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
|
||||||
public DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate,
|
public DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate,
|
||||||
|
List<Module> modules,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelUIService dataModelUIService,
|
IDataModelUIService dataModelUIService,
|
||||||
IConditionOperatorService conditionOperatorService,
|
IConditionOperatorService conditionOperatorService,
|
||||||
ISettingsService settingsService)
|
ISettingsService settingsService)
|
||||||
: base(dataModelConditionGeneralPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
: base(dataModelConditionGeneralPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
||||||
{
|
{
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
Background="#7B7B7B"
|
Background="#7B7B7B"
|
||||||
BorderBrush="#7B7B7B"
|
BorderBrush="#7B7B7B"
|
||||||
Content="{Binding SelectedOperator.Description}"
|
Content="{Binding SelectedOperator.Description}"
|
||||||
|
IsEnabled="{Binding CanSelectOperator}"
|
||||||
Click="PropertyButton_OnClick">
|
Click="PropertyButton_OnClick">
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu ItemsSource="{Binding Operators}">
|
<ContextMenu ItemsSource="{Binding Operators}">
|
||||||
|
|||||||
@ -3,8 +3,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Extensions;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
@ -16,11 +16,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
|
||||||
public DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate,
|
public DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate,
|
||||||
|
List<Module> modules,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelUIService dataModelUIService,
|
IDataModelUIService dataModelUIService,
|
||||||
IConditionOperatorService conditionOperatorService,
|
IConditionOperatorService conditionOperatorService,
|
||||||
ISettingsService settingsService)
|
ISettingsService settingsService)
|
||||||
: base(dataModelConditionListPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
: base(dataModelConditionListPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
||||||
{
|
{
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
|
|
||||||
@ -44,6 +45,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateModules()
|
||||||
|
{
|
||||||
|
foreach (DataModelConditionViewModel dataModelConditionViewModel in Items)
|
||||||
|
dataModelConditionViewModel.UpdateModules();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnInitialActivate()
|
protected override void OnInitialActivate()
|
||||||
{
|
{
|
||||||
base.OnInitialActivate();
|
base.OnInitialActivate();
|
||||||
@ -81,10 +93,5 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
|
|
||||||
return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(true));
|
return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Evaluate()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Dialogs.ProfileCreateView"
|
|
||||||
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:s="https://github.com/canton7/Stylet"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="213.053" d:DesignWidth="254.425">
|
|
||||||
<StackPanel Margin="16">
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
|
|
||||||
Add a new profile
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBox materialDesign:HintAssist.Hint="Profile name"
|
|
||||||
Margin="0 8 0 16"
|
|
||||||
Width="300"
|
|
||||||
Style="{StaticResource MaterialDesignFilledTextBox}"
|
|
||||||
Text="{Binding ProfileName, UpdateSourceTrigger=PropertyChanged}" />
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 8 0 0">
|
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 0 8 0" Command="{s:Action Cancel}">
|
|
||||||
CANCEL
|
|
||||||
</Button>
|
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 0 0 0" Command="{s:Action Accept}">
|
|
||||||
ACCEPT
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using FluentValidation;
|
|
||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
|
|
||||||
{
|
|
||||||
public class ProfileCreateViewModel : DialogViewModelBase
|
|
||||||
{
|
|
||||||
private string _profileName;
|
|
||||||
|
|
||||||
public ProfileCreateViewModel(IModelValidator<ProfileCreateViewModel> validator) : base(validator)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ProfileName
|
|
||||||
{
|
|
||||||
get => _profileName;
|
|
||||||
set => SetAndNotify(ref _profileName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Accept()
|
|
||||||
{
|
|
||||||
await ValidateAsync();
|
|
||||||
|
|
||||||
if (HasErrors)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Session.Close(ProfileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProfileCreateViewModelValidator : AbstractValidator<ProfileCreateViewModel>
|
|
||||||
{
|
|
||||||
public ProfileCreateViewModelValidator()
|
|
||||||
{
|
|
||||||
RuleFor(m => m.ProfileName).NotEmpty().WithMessage("Profile name may not be empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using FluentValidation;
|
|
||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
|
|
||||||
{
|
|
||||||
public class ProfileEditViewModel : DialogViewModelBase
|
|
||||||
{
|
|
||||||
private string _profileName;
|
|
||||||
|
|
||||||
public ProfileEditViewModel(IModelValidator<ProfileEditViewModel> validator, Profile profile) : base(validator)
|
|
||||||
{
|
|
||||||
ProfileName = profile.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ProfileName
|
|
||||||
{
|
|
||||||
get => _profileName;
|
|
||||||
set => SetAndNotify(ref _profileName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Accept()
|
|
||||||
{
|
|
||||||
await ValidateAsync();
|
|
||||||
|
|
||||||
if (HasErrors)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Session.Close(ProfileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProfileEditViewModelValidator : AbstractValidator<ProfileEditViewModel>
|
|
||||||
{
|
|
||||||
public ProfileEditViewModelValidator()
|
|
||||||
{
|
|
||||||
RuleFor(m => m.ProfileName).NotEmpty().WithMessage("Profile name may not be empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Dialogs.ProfileExportView"
|
|
||||||
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:s="https://github.com/canton7/Stylet"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<Grid Margin="16" Width="500">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Grid.Row="0">
|
|
||||||
Export current profile
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="1"
|
|
||||||
Margin="0 10"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
|
||||||
Foreground="{DynamicResource MaterialDesignBodyLight}">
|
|
||||||
It looks like you have not set up any profile adaption hints. This means Artemis can't do much to make your profile look good on a different surface other than try finding the same LEDs as you have.
|
|
||||||
<LineBreak/><LineBreak/>
|
|
||||||
To configure adaption hints, right-click on a layer and choose <materialDesign:PackIcon Kind="AutoFix" /> <Run FontWeight="Bold">View Adaption Hints</Run>.
|
|
||||||
<LineBreak/><LineBreak/>
|
|
||||||
To learn more about profile adaption, check out
|
|
||||||
<Hyperlink Style="{StaticResource ArtemisHyperlink}" RequestNavigate="{s:Action OpenHyperlink}" NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints">
|
|
||||||
this wiki article
|
|
||||||
</Hyperlink>.
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Row="2">
|
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 8 8 0" Command="{s:Action Cancel}">
|
|
||||||
CANCEL
|
|
||||||
</Button>
|
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 0 0" Command="{s:Action Accept}">
|
|
||||||
EXPORT ANYWAY
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using System.Windows.Navigation;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
|
|
||||||
{
|
|
||||||
public class ProfileExportViewModel : DialogViewModelBase
|
|
||||||
{
|
|
||||||
private readonly IProfileService _profileService;
|
|
||||||
private readonly IMessageService _messageService;
|
|
||||||
|
|
||||||
public ProfileExportViewModel(ProfileDescriptor profileDescriptor, IProfileService profileService, IMessageService messageService)
|
|
||||||
{
|
|
||||||
ProfileDescriptor = profileDescriptor;
|
|
||||||
|
|
||||||
_profileService = profileService;
|
|
||||||
_messageService = messageService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProfileDescriptor ProfileDescriptor { get; }
|
|
||||||
|
|
||||||
#region Overrides of Screen
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnActivate()
|
|
||||||
{
|
|
||||||
// TODO: If the profile has hints on all layers, call Accept
|
|
||||||
base.OnActivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
|
|
||||||
{
|
|
||||||
Core.Utilities.OpenUrl(e.Uri.AbsoluteUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Accept()
|
|
||||||
{
|
|
||||||
string encoded = _profileService.ExportProfile(ProfileDescriptor);
|
|
||||||
Clipboard.SetText(encoded);
|
|
||||||
_messageService.ShowMessage("Profile contents exported to clipboard.");
|
|
||||||
|
|
||||||
Session.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Dialogs.ProfileImportView"
|
|
||||||
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:s="https://github.com/canton7/Stylet"
|
|
||||||
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<Grid Margin="16" Width="800">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Grid.Row="0">
|
|
||||||
Import profile to current module
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="1"
|
|
||||||
Margin="0 10"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
|
||||||
Foreground="{DynamicResource MaterialDesignBodyLight}">
|
|
||||||
Please note that importing profiles like this is placeholder functionality. The idea is that this will eventually happen via the workshop.
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="2"
|
|
||||||
Margin="0 10"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
|
||||||
Foreground="{DynamicResource MaterialDesignBodyLight}">
|
|
||||||
The workshop will include tools to make profiles convert easily and look good on different layouts.
|
|
||||||
That means right now when you import this profile unless you have the exact same setup as
|
|
||||||
the person who exported it, you'll have to select LEDs for each layer in the profile.
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBox Grid.Row="3"
|
|
||||||
Text="{Binding ProfileJson}"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
|
||||||
FontFamily="Consolas"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Height="400"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
materialDesign:HintAssist.Hint="Paste profile JSON here"
|
|
||||||
Margin="16" />
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="4">
|
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 8 8 0" Command="{s:Action Cancel}">
|
|
||||||
CANCEL
|
|
||||||
</Button>
|
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 0 0" Command="{s:Action Accept}">
|
|
||||||
I UNDERSTAND, IMPORT
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user