1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2021-06-19 14:54:15 +02:00
commit dd8827e186
58 changed files with 1699 additions and 272 deletions

View File

@ -61,6 +61,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites_005Cprerequisiteaction/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites_005Cprerequisiteaction/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprofiling/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprofiling/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cscriptingproviders_005Cscripts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean>

View File

@ -0,0 +1,15 @@
using System;
namespace Artemis.Core
{
internal class EnumContainsConditionOperator : ConditionOperator<Enum, Enum>
{
public override string Description => "Contains";
public override string Icon => "Contain";
public override bool Evaluate(Enum a, Enum b)
{
return a != null && b != null && a.HasFlag(b);
}
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace Artemis.Core
{
internal class EnumNotContainsConditionOperator : ConditionOperator<Enum, Enum>
{
public override string Description => "Does not contain";
public override string Icon => "FormatStrikethrough";
public override bool Evaluate(Enum a, Enum b)
{
return a != null && (b == null || !a.HasFlag(b));
}
}
}

View File

@ -9,7 +9,7 @@ namespace Artemis.Core
public override bool Evaluate(string a, string b) public override bool Evaluate(string a, string b)
{ {
return a != null && b != null && !a.Contains(b, StringComparison.InvariantCultureIgnoreCase); return a != null && (b == null || !a.Contains(b, StringComparison.InvariantCultureIgnoreCase));
} }
} }
} }

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using RGB.NET.Core; using RGB.NET.Core;
@ -37,6 +38,9 @@ namespace Artemis.Core
Profile = Parent.Profile; Profile = Parent.Profile;
Name = name; Name = name;
Suspended = false; Suspended = false;
Scripts = new List<LayerScript>();
ScriptConfigurations = new List<ScriptConfiguration>();
_general = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
@ -60,6 +64,9 @@ namespace Artemis.Core
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
Scripts = new List<LayerScript>();
ScriptConfigurations = new List<ScriptConfiguration>();
_general = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
@ -75,6 +82,16 @@ namespace Artemis.Core
/// </summary> /// </summary>
public ReadOnlyCollection<ArtemisLed> Leds => _leds.AsReadOnly(); public ReadOnlyCollection<ArtemisLed> Leds => _leds.AsReadOnly();
/// <summary>
/// Gets a collection of all active scripts assigned to this layer
/// </summary>
public List<LayerScript> Scripts { get; }
/// <summary>
/// Gets a collection of all script configurations assigned to this layer
/// </summary>
public List<ScriptConfiguration> ScriptConfigurations { get; }
/// <summary> /// <summary>
/// Defines the shape that is rendered by the <see cref="LayerBrush" />. /// Defines the shape that is rendered by the <see cref="LayerBrush" />.
/// </summary> /// </summary>
@ -142,8 +159,10 @@ namespace Artemis.Core
if (LayerBrush?.BaseProperties != null) if (LayerBrush?.BaseProperties != null)
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties()); result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
foreach (BaseLayerEffect layerEffect in LayerEffects) foreach (BaseLayerEffect layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null) if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties()); result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result; return result;
} }
@ -171,6 +190,9 @@ namespace Artemis.Core
Disposed = true; Disposed = true;
while (Scripts.Count > 1)
Scripts[0].Dispose();
LayerBrushStore.LayerBrushAdded -= LayerBrushStoreOnLayerBrushAdded; LayerBrushStore.LayerBrushAdded -= LayerBrushStoreOnLayerBrushAdded;
LayerBrushStore.LayerBrushRemoved -= LayerBrushStoreOnLayerBrushRemoved; LayerBrushStore.LayerBrushRemoved -= LayerBrushStoreOnLayerBrushRemoved;
@ -247,6 +269,11 @@ namespace Artemis.Core
ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups); ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
LoadRenderElement(); LoadRenderElement();
Adapter.Load(); Adapter.Load();
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
scriptConfiguration.Script?.Dispose();
ScriptConfigurations.Clear();
ScriptConfigurations.AddRange(LayerEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)));
} }
internal override void Save() internal override void Save()
@ -284,6 +311,13 @@ namespace Artemis.Core
// Adaption hints // Adaption hints
Adapter.Save(); Adapter.Save();
LayerEntity.ScriptConfigurations.Clear();
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
{
scriptConfiguration.Save();
LayerEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
}
SaveRenderElement(); SaveRenderElement();
} }
@ -316,6 +350,9 @@ namespace Artemis.Core
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
foreach (LayerScript layerScript in Scripts)
layerScript.OnLayerUpdating(deltaTime);
UpdateDisplayCondition(); UpdateDisplayCondition();
UpdateTimeline(deltaTime); UpdateTimeline(deltaTime);
@ -323,6 +360,9 @@ namespace Artemis.Core
Enable(); Enable();
else if (Timeline.IsFinished) else if (Timeline.IsFinished)
Disable(); Disable();
foreach (LayerScript layerScript in Scripts)
layerScript.OnLayerUpdated(deltaTime);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -473,19 +513,27 @@ namespace Artemis.Core
if (LayerBrush == null) if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering"); throw new ArtemisCoreException("The layer is not yet ready for rendering");
foreach (LayerScript layerScript in Scripts)
layerScript.OnLayerRendering(canvas, bounds, layerPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
baseLayerEffect.PreProcess(canvas, bounds, layerPaint); baseLayerEffect.PreProcess(canvas, bounds, layerPaint);
try try
{ {
canvas.SaveLayer(layerPaint); canvas.SaveLayer(layerPaint);
// If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
canvas.ClipPath(renderPath); canvas.ClipPath(renderPath);
// Restore the blend mode before doing the actual render
layerPaint.BlendMode = SKBlendMode.SrcOver;
LayerBrush.InternalRender(canvas, bounds, layerPaint); LayerBrush.InternalRender(canvas, bounds, layerPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
baseLayerEffect.PostProcess(canvas, bounds, layerPaint); baseLayerEffect.PostProcess(canvas, bounds, layerPaint);
foreach (LayerScript layerScript in Scripts)
layerScript.OnLayerRendered(canvas, bounds, layerPaint);
} }
finally finally
@ -500,9 +548,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
if (!Leds.Any()) if (!Leds.Any())
{
Path = new SKPath(); Path = new SKPath();
}
else else
{ {
SKPath path = new() {FillType = SKPathFillType.Winding}; SKPath path = new() {FillType = SKPathFillType.Winding};

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
namespace Artemis.Core namespace Artemis.Core
@ -23,6 +24,16 @@ namespace Artemis.Core
/// </summary> /// </summary>
LayerPropertyGroup LayerPropertyGroup { get; } LayerPropertyGroup LayerPropertyGroup { get; }
/// <summary>
/// Gets a collection of all active scripts assigned to this layer property
/// </summary>
List<PropertyScript> Scripts { get; }
/// <summary>
/// Gets a collection of all script configurations assigned to this layer property
/// </summary>
public List<ScriptConfiguration> ScriptConfigurations { get; }
/// <summary> /// <summary>
/// Gets the unique path of the property on the layer /// Gets the unique path of the property on the layer
/// </summary> /// </summary>

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -30,6 +31,8 @@ namespace Artemis.Core
Path = null!; Path = null!;
Entity = null!; Entity = null!;
PropertyDescription = null!; PropertyDescription = null!;
Scripts = new List<PropertyScript>();
ScriptConfigurations = new List<ScriptConfiguration>();
CurrentValue = default!; CurrentValue = default!;
DefaultValue = default!; DefaultValue = default!;
@ -54,17 +57,17 @@ namespace Artemis.Core
/// <see langword="false" /> to release only unmanaged resources. /// <see langword="false" /> to release only unmanaged resources.
/// </param> /// </param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{
if (disposing)
{ {
_disposed = true; _disposed = true;
while (Scripts.Count > 1)
Scripts[0].Dispose();
foreach (IDataBinding dataBinding in _dataBindings) foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Dispose(); dataBinding.Dispose();
Disposed?.Invoke(this, EventArgs.Empty); Disposed?.Invoke(this, EventArgs.Empty);
} }
}
/// <summary> /// <summary>
/// Invokes the <see cref="Updated" /> event /// Invokes the <see cref="Updated" /> event
@ -150,6 +153,12 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public PropertyDescriptionAttribute PropertyDescription { get; internal set; } public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
/// <inheritdoc />
public List<PropertyScript> Scripts { get; }
/// <inheritdoc />
public List<ScriptConfiguration> ScriptConfigurations { get; }
/// <inheritdoc /> /// <inheritdoc />
public string Path { get; private set; } public string Path { get; private set; }
@ -162,12 +171,18 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
foreach (PropertyScript propertyScript in Scripts)
propertyScript.OnPropertyUpdating(timeline.Delta.TotalSeconds);
CurrentValue = BaseValue; CurrentValue = BaseValue;
UpdateKeyframes(timeline); UpdateKeyframes(timeline);
UpdateDataBindings(timeline); UpdateDataBindings(timeline);
OnUpdated(); OnUpdated();
foreach (PropertyScript propertyScript in Scripts)
propertyScript.OnPropertyUpdated(timeline.Delta.TotalSeconds);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -747,6 +762,11 @@ namespace Artemis.Core
if (dataBinding != null) if (dataBinding != null)
_dataBindings.Add(dataBinding); _dataBindings.Add(dataBinding);
} }
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
scriptConfiguration.Script?.Dispose();
ScriptConfigurations.Clear();
ScriptConfigurations.AddRange(Entity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)));
} }
/// <summary> /// <summary>
@ -768,6 +788,13 @@ namespace Artemis.Core
Entity.DataBindingEntities.Clear(); Entity.DataBindingEntities.Clear();
foreach (IDataBinding dataBinding in _dataBindings) foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Save(); dataBinding.Save();
Entity.ScriptConfigurations.Clear();
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
{
scriptConfiguration.Save();
Entity.ScriptConfigurations.Add(scriptConfiguration.Entity);
}
} }
/// <summary> /// <summary>

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using SkiaSharp; using SkiaSharp;
@ -20,6 +21,8 @@ namespace Artemis.Core
Profile = this; Profile = this;
ProfileEntity = profileEntity; ProfileEntity = profileEntity;
EntityId = profileEntity.Id; EntityId = profileEntity.Id;
Scripts = new List<ProfileScript>();
ScriptConfigurations = new List<ScriptConfiguration>();
UndoStack = new Stack<string>(); UndoStack = new Stack<string>();
RedoStack = new Stack<string>(); RedoStack = new Stack<string>();
@ -32,6 +35,17 @@ namespace Artemis.Core
/// </summary> /// </summary>
public ProfileConfiguration Configuration { get; } public ProfileConfiguration Configuration { get; }
/// <summary>
/// Gets a collection of all active scripts assigned to this profile
/// </summary>
public List<ProfileScript> Scripts { get; }
/// <summary>
/// Gets a collection of all script configurations assigned to this profile
/// </summary>
public List<ScriptConfiguration> ScriptConfigurations { get; }
/// <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
/// since import /// since import
@ -62,8 +76,14 @@ namespace Artemis.Core
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileUpdating(deltaTime);
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Update(deltaTime); profileElement.Update(deltaTime);
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileUpdated(deltaTime);
} }
} }
@ -75,8 +95,14 @@ namespace Artemis.Core
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Render(canvas, basePosition); profileElement.Render(canvas, basePosition);
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
} }
} }
@ -125,6 +151,9 @@ namespace Artemis.Core
if (!disposing) if (!disposing)
return; return;
while (Scripts.Count > 1)
Scripts[0].Dispose();
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Dispose(); profileElement.Dispose();
ChildrenList.Clear(); ChildrenList.Clear();
@ -157,6 +186,11 @@ namespace Artemis.Core
AddChild(new Folder(this, this, rootFolder)); AddChild(new Folder(this, this, rootFolder));
} }
} }
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
scriptConfiguration.Script?.Dispose();
ScriptConfigurations.Clear();
ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)));
} }
internal override void Save() internal override void Save()
@ -176,6 +210,13 @@ 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));
ProfileEntity.ScriptConfigurations.Clear();
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
{
scriptConfiguration.Save();
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
}
} }
} }
} }

View File

@ -124,8 +124,8 @@ namespace Artemis.Core
public void UpdateTimeline(double deltaTime) public void UpdateTimeline(double deltaTime)
{ {
// The play mode dictates whether to stick to the main segment unless the display conditions contains events // The play mode dictates whether to stick to the main segment unless the display conditions contains events
bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet; bool stickToMainSegment = (Timeline.PlayMode == TimelinePlayMode.Repeat || Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) && DisplayConditionMet;
if (DisplayCondition != null && DisplayCondition.ContainsEvents) if (DisplayCondition != null && DisplayCondition.ContainsEvents && Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle)
stickToMainSegment = false; stickToMainSegment = false;
Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment); Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment);
@ -356,6 +356,7 @@ namespace Artemis.Core
private DataModelConditionGroup? _displayCondition; private DataModelConditionGroup? _displayCondition;
private bool _displayConditionMet; private bool _displayConditionMet;
private bool _toggledOnByEvent = false;
/// <summary> /// <summary>
/// Gets or sets the root display condition group /// Gets or sets the root display condition group
@ -383,6 +384,9 @@ namespace Artemis.Core
return; return;
} }
if (Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle)
_toggledOnByEvent = false;
bool conditionMet = DisplayCondition.Evaluate(); bool conditionMet = DisplayCondition.Evaluate();
if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet) if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet)
conditionMet = false; conditionMet = false;
@ -397,6 +401,14 @@ namespace Artemis.Core
Timeline.JumpToEndSegment(); Timeline.JumpToEndSegment();
} }
else if (conditionMet) else if (conditionMet)
{
if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle)
{
_toggledOnByEvent = !_toggledOnByEvent;
if (_toggledOnByEvent)
Timeline.JumpToStart();
}
else
{ {
// Event conditions reset if the timeline finished // Event conditions reset if the timeline finished
if (Timeline.IsFinished) if (Timeline.IsFinished)
@ -415,8 +427,11 @@ namespace Artemis.Core
// done // done
} }
} }
}
DisplayConditionMet = conditionMet; DisplayConditionMet = Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle
? _toggledOnByEvent
: conditionMet;
} }
#endregion #endregion

View File

@ -155,7 +155,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a boolean indicating whether the timeline has finished its run /// Gets a boolean indicating whether the timeline has finished its run
/// </summary> /// </summary>
public bool IsFinished => (Position > Length || Length == TimeSpan.Zero) && !ExtraTimelines.Any(); public bool IsFinished => Position > Length && !ExtraTimelines.Any();
/// <summary> /// <summary>
/// Gets a boolean indicating whether the timeline progress has been overridden /// Gets a boolean indicating whether the timeline progress has been overridden
@ -516,6 +516,11 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Play another copy of the timeline on top of the current run /// Play another copy of the timeline on top of the current run
/// </summary> /// </summary>
Copy Copy,
/// <summary>
/// Repeat the timeline until the event fires again
/// </summary>
Toggle
} }
} }

View File

@ -4,7 +4,6 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Artemis.Core.Services; using Artemis.Core.Services;
using Ninject;
namespace Artemis.Core.Modules namespace Artemis.Core.Modules
{ {
@ -13,8 +12,6 @@ namespace Artemis.Core.Modules
/// </summary> /// </summary>
public class ProcessActivationRequirement : IModuleActivationRequirement public class ProcessActivationRequirement : IModuleActivationRequirement
{ {
private readonly IProcessMonitorService _processMonitorService;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ProcessActivationRequirement" /> class /// Creates a new instance of the <see cref="ProcessActivationRequirement" /> class
/// </summary> /// </summary>
@ -25,11 +22,6 @@ namespace Artemis.Core.Modules
if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location)) if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location))
throw new ArgumentNullException($"Atleast one {nameof(processName)} and {nameof(location)} must not be null"); throw new ArgumentNullException($"Atleast one {nameof(processName)} and {nameof(location)} must not be null");
// Let's not make a habit out of this :P
if (CoreService.Kernel == null)
throw new ArtemisCoreException("Cannot create a ProcessActivationRequirement before initializing the Core");
_processMonitorService = CoreService.Kernel.Get<IProcessMonitorService>();
ProcessName = processName; ProcessName = processName;
Location = location; Location = location;
} }
@ -44,13 +36,15 @@ namespace Artemis.Core.Modules
/// </summary> /// </summary>
public string? Location { get; set; } public string? Location { get; set; }
internal static IProcessMonitorService? ProcessMonitorService { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public bool Evaluate() public bool Evaluate()
{ {
if (ProcessName == null && Location == null) if (ProcessMonitorService == null || ProcessName == null && Location == null)
return false; return false;
IEnumerable<Process> processes = _processMonitorService.GetRunningProcesses(); IEnumerable<Process> processes = ProcessMonitorService.GetRunningProcesses();
if (ProcessName != null) if (ProcessName != null)
processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase)); processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase));
if (Location != null) if (Location != null)

View File

@ -0,0 +1,86 @@
using Artemis.Storage.Entities.General;
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Represents the configuration of a script
/// </summary>
public class ScriptConfiguration : CorePropertyChanged, IStorageModel
{
private string _scriptingProviderId;
private string _name;
private string? _scriptContent;
/// <summary>
/// Creates a new instance of the <see cref="ScriptConfiguration" /> class
/// </summary>
public ScriptConfiguration(ScriptingProvider provider, string name)
{
ScriptingProviderId = provider.Id;
Name = name;
Entity = new ScriptConfigurationEntity();
}
internal ScriptConfiguration(ScriptConfigurationEntity entity)
{
ScriptingProviderId = null!;
Name = null!;
Entity = entity;
Load();
}
/// <summary>
/// Gets or sets the ID of the scripting provider
/// </summary>
public string ScriptingProviderId
{
get => _scriptingProviderId;
set => SetAndNotify(ref _scriptingProviderId, value);
}
/// <summary>
/// Gets or sets the name of the script
/// </summary>
public string Name
{
get => _name;
set => SetAndNotify(ref _name, value);
}
/// <summary>
/// Gets or sets the script's content
/// </summary>
public string? ScriptContent
{
get => _scriptContent;
set => SetAndNotify(ref _scriptContent, value);
}
/// <summary>
/// If active, gets the script
/// </summary>
public Script? Script { get; internal set; }
internal ScriptConfigurationEntity Entity { get; }
#region Implementation of IStorageModel
/// <inheritdoc />
public void Load()
{
ScriptingProviderId = Entity.ScriptingProviderId;
ScriptContent = Entity.ScriptContent;
Name = Entity.Name;
}
/// <inheritdoc />
public void Save()
{
Entity.ScriptingProviderId = ScriptingProviderId;
Entity.ScriptContent = ScriptContent;
}
#endregion
}
}

View File

@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Allows you to implement and register your own scripting provider.
/// </summary>
public abstract class ScriptingProvider<TGlobalScript, TProfileScript, TLayerScript, TPropertyScript> : ScriptingProvider
where TGlobalScript : GlobalScript
where TProfileScript : ProfileScript
where TLayerScript : LayerScript
where TPropertyScript : PropertyScript
{
/// <summary>
/// Called when the UI needs a script editor for a <see cref="GlobalScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreateGlobalScriptEditor(TGlobalScript script);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="ProfileScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreateProfileScriptEditor(TProfileScript script);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="LayerScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreateLayerScriptScriptEditor(TLayerScript script);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="PropertyScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreatePropertyScriptEditor(TPropertyScript script);
#region Overrides of ScriptingProvider
/// <inheritdoc />
internal override Type GlobalScriptType => typeof(TGlobalScript);
/// <inheritdoc />
internal override Type ProfileScriptType => typeof(TProfileScript);
/// <inheritdoc />
internal override Type LayerScriptType => typeof(TLayerScript);
/// <inheritdoc />
internal override Type PropertyScriptType => typeof(TPropertyScript);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="GlobalScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public override IScriptEditorViewModel CreateGlobalScriptEditor(GlobalScript script)
{
if (script == null) throw new ArgumentNullException(nameof(script));
if (script.GetType() != GlobalScriptType)
throw new ArtemisCoreException($"This scripting provider only supports global scripts of type {GlobalScriptType.Name}");
return CreateGlobalScriptEditor((TGlobalScript) script);
}
/// <summary>
/// Called when the UI needs a script editor for a <see cref="ProfileScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public override IScriptEditorViewModel CreateProfileScriptEditor(ProfileScript script)
{
if (script == null) throw new ArgumentNullException(nameof(script));
if (script.GetType() != ProfileScriptType)
throw new ArtemisCoreException($"This scripting provider only supports profile scripts of type {ProfileScriptType.Name}");
return CreateProfileScriptEditor((TProfileScript) script);
}
/// <summary>
/// Called when the UI needs a script editor for a <see cref="LayerScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public override IScriptEditorViewModel CreateLayerScriptScriptEditor(LayerScript script)
{
if (script == null) throw new ArgumentNullException(nameof(script));
if (script.GetType() != LayerScriptType)
throw new ArtemisCoreException($"This scripting provider only supports layer scripts of type {LayerScriptType.Name}");
return CreateLayerScriptScriptEditor((TLayerScript) script);
}
/// <summary>
/// Called when the UI needs a script editor for a <see cref="PropertyScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public override IScriptEditorViewModel CreatePropertyScriptEditor(PropertyScript script)
{
if (script == null) throw new ArgumentNullException(nameof(script));
if (script.GetType() != PropertyScriptType)
throw new ArtemisCoreException($"This scripting provider only supports property scripts of type {PropertyScriptType.Name}");
return CreatePropertyScriptEditor((TPropertyScript) script);
}
#endregion
#region Overrides of PluginFeature
/// <inheritdoc />
internal override void InternalDisable()
{
base.InternalDisable();
while (Scripts.Count > 0)
Scripts[0].Dispose();
}
#endregion
}
/// <summary>
/// Allows you to implement and register your own scripting provider.
/// <para>
/// Note: You can't implement this, implement
/// <see cref="ScriptingProvider{TProfileScript,TLayerScript,TPropertyScript,TGlobalScript}" /> instead.
/// </para>
/// </summary>
public abstract class ScriptingProvider : PluginFeature
{
internal abstract Type GlobalScriptType { get; }
internal abstract Type PropertyScriptType { get; }
internal abstract Type LayerScriptType { get; }
internal abstract Type ProfileScriptType { get; }
internal List<Script> InternalScripts { get; } = new();
/// <summary>
/// Gets a list of all active scripts belonging to this scripting provider
/// </summary>
public ReadOnlyCollection<Script> Scripts => InternalScripts.AsReadOnly();
/// <summary>
/// Called when the UI needs a script editor for a <see cref="GlobalScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreateGlobalScriptEditor(GlobalScript script);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="ProfileScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreateProfileScriptEditor(ProfileScript script);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="LayerScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreateLayerScriptScriptEditor(LayerScript script);
/// <summary>
/// Called when the UI needs a script editor for a <see cref="PropertyScript" />
/// </summary>
/// <param name="script">The script the editor must edit</param>
public abstract IScriptEditorViewModel CreatePropertyScriptEditor(PropertyScript script);
}
/// <summary>
/// Represents a view model containing a script editor
/// </summary>
public interface IScriptEditorViewModel
{
/// <summary>
/// Gets the script this editor is editing
/// </summary>
Script Script { get; }
}
}

View File

@ -0,0 +1,43 @@
using Artemis.Core.Services;
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Represents a script running globally
/// </summary>
public abstract class GlobalScript : Script
{
/// <inheritdoc />
protected GlobalScript(ScriptConfiguration configuration) : base(configuration)
{
}
internal ScriptingService ScriptingService { get; set; }
/// <summary>
/// Called whenever the Artemis Core is about to update
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnCoreUpdating(double deltaTime)
{
}
/// <summary>
/// Called whenever the Artemis Core has been updated
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnCoreUpdated(double deltaTime)
{
}
#region Overrides of Script
/// <inheritdoc />
internal override void InternalCleanup()
{
ScriptingService.InternalGlobalScripts.Remove(this);
}
#endregion
}
}

View File

@ -0,0 +1,70 @@
using SkiaSharp;
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Represents a script bound to a specific <see cref="Layer" /> processed by a <see cref="ScriptingProvider" />.
/// </summary>
public abstract class LayerScript : Script
{
/// <inheritdoc />
protected LayerScript(Layer layer, ScriptConfiguration configuration) : base(configuration)
{
Layer = layer;
}
/// <summary>
/// Gets the layer this script is bound to
/// </summary>
public Layer Layer { get; internal set; }
/// <summary>
/// Called whenever the layer is about to update
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnLayerUpdating(double deltaTime)
{
}
/// <summary>
/// Called whenever the layer has been updated
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnLayerUpdated(double deltaTime)
{
}
/// <summary>
/// Called whenever the layer is about to render
/// </summary>
/// <param name="canvas">The layer canvas</param>
/// <param name="bounds">The area to be filled, covers the shape</param>
/// <param name="paint">The paint to be used to fill the shape</param>
public virtual void OnLayerRendering(SKCanvas canvas, SKRect bounds, SKPaint paint)
{
}
/// <summary>
/// Called whenever the layer has been rendered
/// </summary>
/// <param name="canvas">The layer canvas</param>
/// <param name="bounds">The area to be filled, covers the shape</param>
/// <param name="paint">The paint to be used to fill the shape</param>
public virtual void OnLayerRendered(SKCanvas canvas, SKRect bounds, SKPaint paint)
{
}
#region Overrides of Script
/// <inheritdoc />
internal override void InternalCleanup()
{
lock (Layer.Scripts)
{
Layer.Scripts.Remove(this);
}
}
#endregion
}
}

View File

@ -0,0 +1,68 @@
using SkiaSharp;
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Represents a script bound to a specific <see cref="Profile" /> processed by a <see cref="ScriptingProvider" />.
/// </summary>
public abstract class ProfileScript : Script
{
/// <inheritdoc />
protected ProfileScript(Profile profile, ScriptConfiguration configuration) : base(configuration)
{
Profile = profile;
}
/// <summary>
/// Gets the profile this script is bound to
/// </summary>
public Profile Profile { get; }
/// <summary>
/// Called whenever the profile is about to update
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnProfileUpdating(double deltaTime)
{
}
/// <summary>
/// Called whenever the profile has been updated
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnProfileUpdated(double deltaTime)
{
}
/// <summary>
/// Called whenever the profile is about to render
/// </summary>
/// <param name="canvas">The profile canvas</param>
/// <param name="bounds">The area to be filled, covers the entire canvas</param>
public virtual void OnProfileRendering(SKCanvas canvas, SKRect bounds)
{
}
/// <summary>
/// Called whenever the profile has been rendered
/// </summary>
/// <param name="canvas">The profile canvas</param>
/// <param name="bounds">The area to be filled, covers the entire canvas</param>
public virtual void OnProfileRendered(SKCanvas canvas, SKRect bounds)
{
}
#region Overrides of Script
/// <inheritdoc />
internal override void InternalCleanup()
{
lock (Profile.Scripts)
{
Profile.Scripts.Remove(this);
}
}
#endregion
}
}

View File

@ -0,0 +1,49 @@
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Represents a script bound to a specific <see cref="LayerProperty{T}" /> processed by a
/// <see cref="ScriptingProvider" />.
/// </summary>
public abstract class PropertyScript : Script
{
/// <inheritdoc />
protected PropertyScript(ILayerProperty layerProperty, ScriptConfiguration configuration) : base(configuration)
{
LayerProperty = layerProperty;
}
/// <summary>
/// Gets the layer property this script is bound to
/// </summary>
public ILayerProperty LayerProperty { get; }
/// <summary>
/// Called whenever the property is about to update
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnPropertyUpdating(double deltaTime)
{
}
/// <summary>
/// Called whenever the property has been updated
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnPropertyUpdated(double deltaTime)
{
}
#region Overrides of Script
/// <inheritdoc />
internal override void InternalCleanup()
{
lock (LayerProperty.Scripts)
{
LayerProperty.Scripts.Remove(this);
}
}
#endregion
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.ComponentModel;
namespace Artemis.Core.ScriptingProviders
{
/// <summary>
/// Represents a script processed by a <see cref="ScriptingProviders.ScriptingProvider" />.
/// </summary>
public abstract class Script : CorePropertyChanged, IDisposable
{
private ScriptingProvider _scriptingProvider = null!;
/// <summary>
/// The base constructor of any script
/// </summary>
/// <param name="configuration">The script configuration this script belongs to</param>
protected Script(ScriptConfiguration configuration)
{
if (configuration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptConfiguration = configuration;
ScriptConfiguration.Script = this;
ScriptConfiguration.PropertyChanged += ScriptConfigurationOnPropertyChanged;
}
/// <summary>
/// Gets the scripting provider this script belongs to
/// </summary>
public ScriptingProvider ScriptingProvider
{
get => _scriptingProvider;
internal set => SetAndNotify(ref _scriptingProvider, value);
}
/// <summary>
/// Gets the script configuration this script belongs to
/// </summary>
public ScriptConfiguration ScriptConfiguration { get; }
#region Event handlers
private void ScriptConfigurationOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ScriptConfiguration.ScriptContent))
OnScriptContentChanged();
}
#endregion
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <inheritdoc />
public void Dispose()
{
ScriptConfiguration.PropertyChanged -= ScriptConfigurationOnPropertyChanged;
ScriptConfiguration.Script = null;
ScriptingProvider.InternalScripts.Remove(this);
// Can't trust those pesky plugin devs!
InternalCleanup();
Dispose(true);
GC.SuppressFinalize(this);
}
internal abstract void InternalCleanup();
#endregion
#region Events
/// <summary>
/// Occurs when the contents of the script have changed
/// </summary>
public event EventHandler? ScriptContentChanged;
/// <summary>
/// Invokes the <see cref="ScriptContentChanged" /> event
/// </summary>
protected virtual void OnScriptContentChanged()
{
ScriptContentChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage; using Artemis.Storage;
using HidSharp; using HidSharp;
using Ninject; using Ninject;
@ -29,6 +30,7 @@ namespace Artemis.Core.Services
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly IModuleService _moduleService; private readonly IModuleService _moduleService;
private readonly IScriptingService _scriptingService;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private readonly List<Exception> _updateExceptions = new(); private readonly List<Exception> _updateExceptions = new();
private DateTime _lastExceptionLog; private DateTime _lastExceptionLog;
@ -41,7 +43,8 @@ namespace Artemis.Core.Services
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IRgbService rgbService, IRgbService rgbService,
IProfileService profileService, IProfileService profileService,
IModuleService moduleService) IModuleService moduleService,
IScriptingService scriptingService)
{ {
Kernel = kernel; Kernel = kernel;
Constants.CorePlugin.Kernel = kernel; Constants.CorePlugin.Kernel = kernel;
@ -51,6 +54,7 @@ namespace Artemis.Core.Services
_rgbService = rgbService; _rgbService = rgbService;
_profileService = profileService; _profileService = profileService;
_moduleService = moduleService; _moduleService = moduleService;
_scriptingService = scriptingService;
_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>();
@ -107,6 +111,9 @@ namespace Artemis.Core.Services
{ {
_frameStopWatch.Restart(); _frameStopWatch.Restart();
foreach (GlobalScript script in _scriptingService.GlobalScripts)
script.OnCoreUpdating(args.DeltaTime);
_moduleService.UpdateActiveModules(args.DeltaTime); _moduleService.UpdateActiveModules(args.DeltaTime);
SKTexture texture = _rgbService.OpenRender(); SKTexture texture = _rgbService.OpenRender();
SKCanvas canvas = texture.Surface.Canvas; SKCanvas canvas = texture.Surface.Canvas;
@ -126,6 +133,9 @@ namespace Artemis.Core.Services
canvas.Flush(); canvas.Flush();
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface)); OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
foreach (GlobalScript script in _scriptingService.GlobalScripts)
script.OnCoreUpdated(args.DeltaTime);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Timers; using System.Timers;
using Artemis.Core.Modules;
namespace Artemis.Core.Services namespace Artemis.Core.Services
{ {
@ -22,6 +23,8 @@ namespace Artemis.Core.Services
_processScanTimer.Elapsed += OnTimerElapsed; _processScanTimer.Elapsed += OnTimerElapsed;
_processScanTimer.Start(); _processScanTimer.Start();
_comparer = new ProcessComparer(); _comparer = new ProcessComparer();
ProcessActivationRequirement.ProcessMonitorService = this;
} }
public event EventHandler<ProcessEventArgs>? ProcessStarted; public event EventHandler<ProcessEventArgs>? ProcessStarted;

View File

@ -64,6 +64,10 @@ namespace Artemis.Core.Services
RegisterConditionOperator(Constants.CorePlugin, new StringNullConditionOperator()); RegisterConditionOperator(Constants.CorePlugin, new StringNullConditionOperator());
RegisterConditionOperator(Constants.CorePlugin, new StringNotNullConditionOperator()); RegisterConditionOperator(Constants.CorePlugin, new StringNotNullConditionOperator());
// Enum operators
RegisterConditionOperator(Constants.CorePlugin, new EnumContainsConditionOperator());
RegisterConditionOperator(Constants.CorePlugin, new EnumNotContainsConditionOperator());
// Null checks, at the bottom // Null checks, at the bottom
// TODO: Implement a priority mechanism // TODO: Implement a priority mechanism
RegisterConditionOperator(Constants.CorePlugin, new NullConditionOperator()); RegisterConditionOperator(Constants.CorePlugin, new NullConditionOperator());

View File

@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Artemis.Core.ScriptingProviders;
using Ninject;
using Ninject.Parameters;
namespace Artemis.Core.Services
{
internal class ScriptingService : IScriptingService
{
private readonly IPluginManagementService _pluginManagementService;
private List<ScriptingProvider> _scriptingProviders;
public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
{
_pluginManagementService = pluginManagementService;
InternalGlobalScripts = new List<GlobalScript>();
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
// No need to sub to Deactivated, scripts will deactivate themselves
profileService.ProfileActivated += ProfileServiceOnProfileActivated;
foreach (ProfileConfiguration profileConfiguration in profileService.ProfileConfigurations)
{
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
}
internal List<GlobalScript> InternalGlobalScripts { get; }
private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value)
{
// Limit to one constructor, there's no need to have more and it complicates things anyway
ConstructorInfo[] constructors = scriptType.GetConstructors();
if (constructors.Length != 1)
throw new ArtemisCoreException("Scripts must have exactly one constructor");
// Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure
ParameterInfo configurationParameter = constructors.First().GetParameters().First(p => value.GetType().IsAssignableFrom(p.ParameterType));
if (configurationParameter.Name == null)
throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {scriptType.Name} with type {value.GetType().Name}");
return new ConstructorArgument(configurationParameter.Name, value);
}
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
{
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
}
private void ProfileServiceOnProfileActivated(object? sender, ProfileConfigurationEventArgs e)
{
if (e.ProfileConfiguration.Profile != null)
InitializeProfileScripts(e.ProfileConfiguration.Profile);
}
private void InitializeProfileScripts(Profile profile)
{
// Initialize the scripts on the profile
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null))
CreateScriptInstance(profile, scriptConfiguration);
foreach (Layer layer in profile.GetAllLayers())
{
// Initialize the scripts on the layers
foreach (ScriptConfiguration scriptConfiguration in layer.ScriptConfigurations.Where(c => c.Script == null))
CreateScriptInstance(layer, scriptConfiguration);
// Initialize the scripts on the layer properties of layers
foreach (ILayerProperty layerProperty in layer.GetAllLayerProperties())
{
foreach (ScriptConfiguration scriptConfiguration in layerProperty.ScriptConfigurations.Where(c => c.Script == null))
CreateScriptInstance(layerProperty, scriptConfiguration);
}
}
foreach (Folder folder in profile.GetAllFolders())
{
// Initialize the scripts on the layer properties of folders
foreach (ILayerProperty layerProperty in folder.GetAllLayerProperties())
{
foreach (ScriptConfiguration scriptConfiguration in layerProperty.ScriptConfigurations.Where(c => c.Script == null))
CreateScriptInstance(layerProperty, scriptConfiguration);
}
}
}
public ReadOnlyCollection<GlobalScript> GlobalScripts => InternalGlobalScripts.AsReadOnly();
public GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
return null;
GlobalScript script = (GlobalScript) provider.Plugin.Kernel!.Get(
provider.GlobalScriptType,
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
script.ScriptingService = this;
provider.InternalScripts.Add(script);
InternalGlobalScripts.Add(script);
return script;
}
public ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration)
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
return null;
ProfileScript script = (ProfileScript) provider.Plugin.Kernel!.Get(
provider.ProfileScriptType,
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
provider.InternalScripts.Add(script);
return script;
}
public LayerScript? CreateScriptInstance(Layer layer, ScriptConfiguration scriptConfiguration)
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
return null;
LayerScript script = (LayerScript) provider.Plugin.Kernel!.Get(
provider.LayerScriptType,
CreateScriptConstructorArgument(provider.LayerScriptType, layer),
CreateScriptConstructorArgument(provider.LayerScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
provider.InternalScripts.Add(script);
return script;
}
public PropertyScript? CreateScriptInstance(ILayerProperty layerProperty, ScriptConfiguration scriptConfiguration)
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
return null;
PropertyScript script = (PropertyScript) provider.Plugin.Kernel!.Get(
provider.PropertyScriptType,
CreateScriptConstructorArgument(provider.PropertyScriptType, layerProperty),
CreateScriptConstructorArgument(provider.PropertyScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
provider.InternalScripts.Add(script);
return script;
}
}
public interface IScriptingService : IArtemisService
{
/// <summary>
/// Gets a read only collection of all active global scripts
/// </summary>
ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <summary>
/// Attempts to create an instance of a global script configured in the provided <see cref="ScriptConfiguration" />
/// </summary>
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration);
/// <summary>
/// Attempts to create an instance of a profile script configured in the provided <see cref="ScriptConfiguration" />
/// </summary>
/// <param name="profile">The profile the script is bound to</param>
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration);
/// <summary>
/// Attempts to create an instance of a layer script configured in the provided <see cref="ScriptConfiguration" />
/// </summary>
/// <param name="layer">The layer the script is bound to</param>
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
LayerScript? CreateScriptInstance(Layer layer, ScriptConfiguration scriptConfiguration);
/// <summary>
/// Attempts to create an instance of a layer property script configured in the provided
/// <see cref="ScriptConfiguration" />
/// </summary>
/// <param name="layerProperty">The layer property the script is bound to</param>
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
PropertyScript? CreateScriptInstance(ILayerProperty layerProperty, ScriptConfiguration scriptConfiguration);
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.ObjectModel; using System;
using System.Collections.ObjectModel;
using Newtonsoft.Json; using Newtonsoft.Json;
using SkiaSharp; using SkiaSharp;
@ -158,5 +159,15 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
/// <param name="canvas"></param> /// <param name="canvas"></param>
void RenderProfiles(SKCanvas canvas); void RenderProfiles(SKCanvas canvas);
/// <summary>
/// Occurs whenever a profile has been activated
/// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
/// <summary>
/// Occurs whenever a profile has been deactivated
/// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
} }
} }

View File

@ -317,6 +317,8 @@ namespace Artemis.Core.Services
} }
profileConfiguration.Profile = profile; profileConfiguration.Profile = profile;
OnProfileActivated(new ProfileConfigurationEventArgs(profileConfiguration));
return profile; return profile;
} }
@ -330,6 +332,8 @@ namespace Artemis.Core.Services
Profile profile = profileConfiguration.Profile; Profile profile = profileConfiguration.Profile;
profileConfiguration.Profile = null; profileConfiguration.Profile = null;
profile.Dispose(); profile.Dispose();
OnProfileDeactivated(new ProfileConfigurationEventArgs(profileConfiguration));
} }
public void DeleteProfile(ProfileConfiguration profileConfiguration) public void DeleteProfile(ProfileConfiguration profileConfiguration)
@ -582,5 +586,22 @@ namespace Artemis.Core.Services
_profileRepository.Save(profile.ProfileEntity); _profileRepository.Save(profile.ProfileEntity);
} }
#region Events
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
protected virtual void OnProfileActivated(ProfileConfigurationEventArgs e)
{
ProfileActivated?.Invoke(this, e);
}
protected virtual void OnProfileDeactivated(ProfileConfigurationEventArgs e)
{
ProfileDeactivated?.Invoke(this, e);
}
#endregion
} }
} }

View File

@ -0,0 +1,13 @@
using System;
namespace Artemis.Storage.Entities.General
{
public class ScriptConfigurationEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ScriptingProviderId { get; set; }
public string ScriptContent { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Storage.Entities.General;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.AdaptionHints; using Artemis.Storage.Entities.Profile.AdaptionHints;
using LiteDB; using LiteDB;
@ -12,6 +13,7 @@ namespace Artemis.Storage.Entities.Profile
{ {
Leds = new List<LedEntity>(); Leds = new List<LedEntity>();
AdaptionHints = new List<IAdaptionHintEntity>(); AdaptionHints = new List<IAdaptionHintEntity>();
ScriptConfigurations = new List<ScriptConfigurationEntity>();
PropertyEntities = new List<PropertyEntity>(); PropertyEntities = new List<PropertyEntity>();
LayerEffects = new List<LayerEffectEntity>(); LayerEffects = new List<LayerEffectEntity>();
ExpandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
@ -23,6 +25,7 @@ namespace Artemis.Storage.Entities.Profile
public List<LedEntity> Leds { get; set; } public List<LedEntity> Leds { get; set; }
public List<IAdaptionHintEntity> AdaptionHints { get; set; } public List<IAdaptionHintEntity> AdaptionHints { get; set; }
public List<ScriptConfigurationEntity> ScriptConfigurations { get; set; }
[BsonRef("ProfileEntity")] [BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } public ProfileEntity Profile { get; set; }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Storage.Entities.General;
namespace Artemis.Storage.Entities.Profile namespace Artemis.Storage.Entities.Profile
{ {
@ -10,6 +11,7 @@ namespace Artemis.Storage.Entities.Profile
{ {
Folders = new List<FolderEntity>(); Folders = new List<FolderEntity>();
Layers = new List<LayerEntity>(); Layers = new List<LayerEntity>();
ScriptConfigurations = new List<ScriptConfigurationEntity>();
} }
public Guid Id { get; set; } public Guid Id { get; set; }
@ -19,6 +21,7 @@ namespace Artemis.Storage.Entities.Profile
public List<FolderEntity> Folders { get; set; } public List<FolderEntity> Folders { get; set; }
public List<LayerEntity> Layers { get; set; } public List<LayerEntity> Layers { get; set; }
public List<ScriptConfigurationEntity> ScriptConfigurations { get; set; }
public void UpdateGuid(Guid guid) public void UpdateGuid(Guid guid)
{ {

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Storage.Entities.General;
using Artemis.Storage.Entities.Profile.DataBindings; using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Storage.Entities.Profile namespace Artemis.Storage.Entities.Profile
@ -9,6 +10,7 @@ namespace Artemis.Storage.Entities.Profile
{ {
KeyframeEntities = new List<KeyframeEntity>(); KeyframeEntities = new List<KeyframeEntity>();
DataBindingEntities = new List<DataBindingEntity>(); DataBindingEntities = new List<DataBindingEntity>();
ScriptConfigurations = new List<ScriptConfigurationEntity>();
} }
public string FeatureId { get; set; } public string FeatureId { get; set; }
@ -19,5 +21,6 @@ namespace Artemis.Storage.Entities.Profile
public List<KeyframeEntity> KeyframeEntities { get; set; } public List<KeyframeEntity> KeyframeEntities { get; set; }
public List<DataBindingEntity> DataBindingEntities { get; set; } public List<DataBindingEntity> DataBindingEntities { get; set; }
public List<ScriptConfigurationEntity> ScriptConfigurations { get; set; }
} }
} }

View File

@ -36,7 +36,7 @@
<PackageReference Include="Ben.Demystifier" Version="0.3.0" /> <PackageReference Include="Ben.Demystifier" Version="0.3.0" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" /> <PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.0.0" /> <PackageReference Include="MaterialDesignThemes" Version="4.1.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" />
<PackageReference Include="Ninject" Version="3.3.4" /> <PackageReference Include="Ninject" Version="3.3.4" />
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" /> <PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />

View File

@ -44,10 +44,10 @@
Grid.Row="1" Grid.Row="1"
OpacityMask="{x:Null}"> OpacityMask="{x:Null}">
<Track.DecreaseRepeatButton> <Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{DynamicResource MaterialDesignHorizontalColorSliderTrackRepeatButton}" /> <RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource MaterialDesignRepeatButton}" />
</Track.DecreaseRepeatButton> </Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton> <Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{DynamicResource MaterialDesignHorizontalColorSliderTrackRepeatButton}" /> <RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource MaterialDesignRepeatButton}" />
</Track.IncreaseRepeatButton> </Track.IncreaseRepeatButton>
<Track.Thumb> <Track.Thumb>
<Thumb x:Name="Thumb" Width="20" Height="20" VerticalAlignment="Center" Focusable="False" OverridesDefaultStyle="True" <Thumb x:Name="Thumb" Width="20" Height="20" VerticalAlignment="Center" Focusable="False" OverridesDefaultStyle="True"

View File

@ -0,0 +1,20 @@
using Artemis.Core.ScriptingProviders;
using Stylet;
namespace Artemis.UI.Shared.ScriptingProviders
{
/// <summary>
/// Represents a Stylet view model containing a script editor
/// </summary>
public class ScriptEditorViewModelViewModel : Screen, IScriptEditorViewModel
{
/// <inheritdoc />
public ScriptEditorViewModelViewModel(Script script)
{
Script = script;
}
/// <inheritdoc />
public Script Script { get; }
}
}

View File

@ -29,11 +29,11 @@
}, },
"MaterialDesignThemes": { "MaterialDesignThemes": {
"type": "Direct", "type": "Direct",
"requested": "[4.0.0, )", "requested": "[4.1.0, )",
"resolved": "4.0.0", "resolved": "4.1.0",
"contentHash": "+n5oWHuRiYL/gUw2XfQHCRZqHtU8KbrdurgU0IcO98Zsyhw4BvggodfXY8veRtbjjmM9EJ/sG2yKBrgPOGX4JQ==", "contentHash": "WqrO9AbtdE4pLPtDk/C5BZRnkgWFwVGyUHWj7tRJrgnKl089DEobVXBCLeqp2mkgBeFHj4Xe3AfWyhmlnO6AZA==",
"dependencies": { "dependencies": {
"MaterialDesignColors": "2.0.0" "MaterialDesignColors": "2.0.1"
} }
}, },
"Microsoft.Xaml.Behaviors.Wpf": { "Microsoft.Xaml.Behaviors.Wpf": {
@ -150,8 +150,8 @@
}, },
"MaterialDesignColors": { "MaterialDesignColors": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.0.0", "resolved": "2.0.1",
"contentHash": "+JoghC3QRK0u9Wul1To1ORjcfTbFTVzFPjJ02H7VREOdNzIIn427e8G9gP9hXu9pm1r2OneLnoCG/lTma5cG2w==" "contentHash": "Azl8nN23SD6QPE0PdsfpKiIqWTvH7rzXwgXPiFSEt91NFOrwB5cx3iq/sbINWMZunhXJ32+jVUHiV03B8eJbZw=="
}, },
"McMaster.NETCore.Plugins": { "McMaster.NETCore.Plugins": {
"type": "Transitive", "type": "Transitive",

View File

@ -143,7 +143,7 @@
<PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.18" /> <PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.18" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" /> <PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.0.0" /> <PackageReference Include="MaterialDesignThemes" Version="4.1.0" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.0.2" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.0.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" />

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Screens.Header;
using Artemis.UI.Screens.Plugins; using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.ProfileEditor.Conditions; using Artemis.UI.Screens.ProfileEditor.Conditions;
using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties;
@ -60,6 +61,11 @@ namespace Artemis.UI.Ninject.Factories
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(KeyboardSectionAdaptionHint adaptionHint); KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(KeyboardSectionAdaptionHint adaptionHint);
} }
public interface IHeaderVmFactory : IVmFactory
{
SimpleHeaderViewModel SimpleHeaderViewModel(string displayName);
}
public interface IProfileLayerVmFactory : IVmFactory public interface IProfileLayerVmFactory : IVmFactory
{ {
ProfileLayerViewModel Create(Layer layer, PanZoomViewModel panZoomViewModel); ProfileLayerViewModel Create(Layer layer, PanZoomViewModel panZoomViewModel);

View File

@ -33,7 +33,7 @@ namespace Artemis.UI.Ninject
{ {
x.FromThisAssembly() x.FromThisAssembly()
.SelectAllClasses() .SelectAllClasses()
.InheritedFrom<IMainScreenViewModel>() .InheritedFrom<MainScreenViewModel>()
.BindAllBaseClasses() .BindAllBaseClasses()
.Configure(c => c.InSingletonScope()); .Configure(c => c.InSingletonScope());
}); });

View File

@ -0,0 +1,35 @@
<UserControl x:Class="Artemis.UI.Screens.Header.SimpleHeaderView"
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:local="clr-namespace:Artemis.UI.Screens.Header"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SimpleHeaderViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding DisplayName}"
VerticalAlignment="Center"
FontSize="20"
Margin="15 0" />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Text="{Binding FrameTime}" VerticalAlignment="Center" FontSize="14" Margin="10 0" ToolTip="The time the last frame took to render" />
<!-- Bug: materialDesign:RippleAssist.RippleOnTop doesn't look as nice but otherwise it doesn't work at all, not sure why -->
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
VerticalAlignment="Center"
ToolTip="Open debugger"
Command="{s:Action ShowDebugger}"
materialDesign:RippleAssist.RippleOnTop="True">
<materialDesign:PackIcon Kind="Matrix" />
</Button>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,69 @@
using System.Timers;
using Artemis.Core.Services;
using Artemis.UI.Services;
using Stylet;
namespace Artemis.UI.Screens.Header
{
public class SimpleHeaderViewModel : Screen
{
private readonly ICoreService _coreService;
private readonly IDebugService _debugService;
private string _frameTime;
private Timer _frameTimeUpdateTimer;
public SimpleHeaderViewModel(string displayName, ICoreService coreService, IDebugService debugService)
{
DisplayName = displayName;
_coreService = coreService;
_debugService = debugService;
}
public string FrameTime
{
get => _frameTime;
set => SetAndNotify(ref _frameTime, value);
}
public void ShowDebugger()
{
_debugService.ShowDebugger();
}
private void UpdateFrameTime()
{
FrameTime = $"Frame time: {_coreService.FrameTime.TotalMilliseconds:F2} ms";
}
private void OnFrameTimeUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
{
UpdateFrameTime();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
_frameTimeUpdateTimer = new Timer(500);
_frameTimeUpdateTimer.Elapsed += OnFrameTimeUpdateTimerOnElapsed;
_frameTimeUpdateTimer.Start();
UpdateFrameTime();
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
_frameTimeUpdateTimer.Elapsed -= OnFrameTimeUpdateTimerOnElapsed;
_frameTimeUpdateTimer?.Dispose();
_frameTimeUpdateTimer = null;
base.OnClose();
}
#endregion
}
}

View File

@ -1,12 +1,13 @@
using Stylet; using Artemis.UI.Ninject.Factories;
namespace Artemis.UI.Screens.Home namespace Artemis.UI.Screens.Home
{ {
public class HomeViewModel : Screen, IMainScreenViewModel public class HomeViewModel : MainScreenViewModel
{ {
public HomeViewModel() public HomeViewModel(IHeaderVmFactory headerVmFactory)
{ {
DisplayName = "Home"; DisplayName = "Home";
HeaderViewModel = headerVmFactory.SimpleHeaderViewModel(DisplayName);
} }
public void OpenUrl(string url) public void OpenUrl(string url)

View File

@ -1,6 +1,19 @@
namespace Artemis.UI.Screens using Stylet;
namespace Artemis.UI.Screens
{ {
public interface IMainScreenViewModel public abstract class MainScreenViewModel : Screen
{ {
private Screen _headerViewModel;
public Screen HeaderViewModel
{
get => _headerViewModel;
set
{
if (!SetAndNotify(ref _headerViewModel, value)) return;
_headerViewModel.ConductWith(this);
}
}
} }
} }

View File

@ -192,11 +192,11 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Trigger mode --> <!-- Trigger mode -->
<TextBlock Grid.Column="0" Text="Rapid trigger mode"> <TextBlock Grid.Column="0" Text="Trigger mode">
<TextBlock.ToolTip> <TextBlock.ToolTip>
<ToolTip Placement="Center" VerticalOffset="-30"> <ToolTip Placement="Center" VerticalOffset="-30">
<TextBlock> <TextBlock>
Configure how the layer should act when the event(s) trigger before the timeline finishes Configure how the layer should act when the event(s) trigger
</TextBlock> </TextBlock>
</ToolTip> </ToolTip>
</TextBlock.ToolTip> </TextBlock.ToolTip>
@ -207,6 +207,7 @@
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<RadioButton Grid.Column="0" <RadioButton Grid.Column="0"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
@ -224,6 +225,21 @@
</RadioButton.ToolTip> </RadioButton.ToolTip>
</RadioButton> </RadioButton>
<RadioButton Grid.Column="1" <RadioButton Grid.Column="1"
Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Toggle}}">
<TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="TrafficLight" VerticalAlignment="Center" Margin="-3 0 0 -3" />
TOGGLE
</TextBlock>
<RadioButton.ToolTip>
<ToolTip Placement="Center" VerticalOffset="-40">
<TextBlock>
Repeat the timeline until the event fires again
</TextBlock>
</ToolTip>
</RadioButton.ToolTip>
</RadioButton>
<RadioButton Grid.Column="2"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
@ -238,7 +254,7 @@
</ToolTip> </ToolTip>
</RadioButton.ToolTip> </RadioButton.ToolTip>
</RadioButton> </RadioButton>
<RadioButton Grid.Column="2" <RadioButton Grid.Column="3"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">

View File

@ -372,6 +372,7 @@
Grid.Column="2" Grid.Column="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Panel.ZIndex="2" Panel.ZIndex="2"
ClipToBounds="False"
Background="{DynamicResource MaterialDesignCardBackground}"> Background="{DynamicResource MaterialDesignCardBackground}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -433,12 +434,11 @@
<Slider Grid.Column="1" <Slider Grid.Column="1"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="10 5" VerticalAlignment="Center"
materialDesign:SliderAssist.OnlyShowFocusVisualWhileDragging="True"
Minimum="31" Minimum="31"
Maximum="350" Maximum="350"
TickFrequency="1" Margin="10 0"
IsSnapToTickEnabled="True"
AutoToolTipPlacement="TopLeft"
Value="{Binding ProfileEditorService.PixelsPerSecond}" Value="{Binding ProfileEditorService.PixelsPerSecond}"
Foreground="{StaticResource SecondaryHueMidBrush}" Foreground="{StaticResource SecondaryHueMidBrush}"
Width="319" /> Width="319" />

View File

@ -7,8 +7,7 @@
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor" xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor"
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:profile="clr-namespace:Artemis.Core;assembly=Artemis.Core"
mc:Ignorable="d" mc:Ignorable="d"
behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True" behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
@ -29,7 +28,136 @@
<KeyBinding Command="{s:Action Redo}" Modifiers="Control" Key="Y" /> <KeyBinding Command="{s:Action Redo}" Modifiers="Control" Key="Y" />
</UserControl.InputBindings> </UserControl.InputBindings>
<Grid Margin="10"> <Grid ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<shared:ProfileConfigurationIcon Grid.Row="0"
Grid.Column="0"
Width="25"
Height="25"
Margin="25 -5 25 0"
ToolTip="{Binding ProfileConfiguration.Name}"
ConfigurationIcon="{Binding ProfileConfiguration.Icon}" />
<Menu Grid.Row="0" Grid.Column="1" IsMainMenu="True" Margin="0 -4 0 0">
<MenuItem Header="_File">
<MenuItem Header="New" Icon="{materialDesign:PackIcon Kind=Plus}">
<MenuItem Header="Folder"
Icon="{materialDesign:PackIcon Kind=Folder}"
Command="{s:Action AddFolder}"
s:View.ActionTarget="{Binding ProfileTreeViewModel}" />
<MenuItem Header="Layer"
Icon="{materialDesign:PackIcon Kind=Layers}"
Command="{s:Action AddLayer}"
s:View.ActionTarget="{Binding ProfileTreeViewModel}" />
</MenuItem>
<Separator />
<MenuItem Header="View properties"
Icon="{materialDesign:PackIcon Kind=Settings}"
Command="{s:Action ViewProperties}"/>
<MenuItem Header="Suspend profile"
IsCheckable="True"
IsChecked="{Binding ProfileConfiguration.IsSuspended}" />
<Separator />
<MenuItem Header="Export profile"
Icon="{materialDesign:PackIcon Kind=Export}"
Command="{s:Action ExportProfile}" />
<MenuItem Header="Duplicate profile"
Icon="{materialDesign:PackIcon Kind=ContentDuplicate}"
Command="{s:Action DuplicateProfile}" />
<Separator />
<MenuItem Header="Delete profile"
Icon="{materialDesign:PackIcon Kind=Trash}"
Command="{s:Action DeleteProfile}" />
</MenuItem>
<MenuItem Header="_Edit" SubmenuOpened="{s:Action EditMenuOpened}">
<MenuItem Header="_Duplicate"
Icon="{materialDesign:PackIcon Kind=ContentDuplicate}"
Command="{s:Action Duplicate}"
InputGestureText="Ctrl+D"
IsEnabled="{Binding HasSelectedElement}"/>
<MenuItem Header="_Copy"
Icon="{materialDesign:PackIcon Kind=ContentCopy}"
Command="{s:Action Copy}"
InputGestureText="Ctrl+C"
IsEnabled="{Binding HasSelectedElement}"/>
<MenuItem Header="_Paste"
Icon="{materialDesign:PackIcon Kind=ContentPaste}"
Command="{s:Action Paste}"
InputGestureText="Ctrl+V" />
</MenuItem>
<MenuItem Header="_Scripting" IsEnabled="False">
<MenuItem Header="_Profile scripts"
Icon="{materialDesign:PackIcon Kind=BookEdit}" />
<MenuItem Header="_Layer scripts"
Icon="{materialDesign:PackIcon Kind=Layers}"
IsEnabled="{Binding HasSelectedElement}"/>
<MenuItem Header="_Property scripts"
Icon="{materialDesign:PackIcon Kind=FormTextbox}"
IsEnabled="{Binding HasSelectedElement}"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="Artemis wiki"
Icon="{materialDesign:PackIcon Kind=BookEdit}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/" />
<Separator />
<MenuItem Header="Editor"
Icon="{materialDesign:PackIcon Kind=Edit}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles" />
<MenuItem Header="Layers"
Icon="{materialDesign:PackIcon Kind=Layers}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers" />
<MenuItem Header="Display conditions"
Icon="{materialDesign:PackIcon Kind=NotEqual}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions" />
<MenuItem Header="Timeline"
Icon="{materialDesign:PackIcon Kind=Stopwatch}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline" />
<MenuItem Header="Data bindings"
Icon="{materialDesign:PackIcon Kind=VectorLink}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings" />
<MenuItem Header="Scripting"
Icon="{materialDesign:PackIcon Kind=CodeJson}"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting" />
<Separator />
<MenuItem Header="Report a bug"
Icon="{materialDesign:PackIcon Kind=Github}"
Command="{s:Action OpenUrl}"
CommandParameter="https://github.com/Artemis-RGB/Artemis/issues" />
<MenuItem Header="Get help on Discord"
Icon="{materialDesign:PackIcon Kind=Discord}"
Command="{s:Action OpenUrl}"
CommandParameter="https://discord.gg/S3MVaC9" />
</MenuItem>
</Menu>
<Button Grid.Row="0"
Grid.Column="2"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Open debugger"
Margin="10 -4 10 0"
Width="34"
Height="34"
Command="{s:Action OpenDebugger}">
<materialDesign:PackIcon Kind="Matrix" Width="20" Height="20" />
</Button>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="10 -5 10 10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<!-- Left side --> <!-- Left side -->
<ColumnDefinition Width="*" MinWidth="100" /> <ColumnDefinition Width="*" MinWidth="100" />
@ -95,4 +223,5 @@
</materialDesign:Card> </materialDesign:Card>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</UserControl> </UserControl>

View File

@ -5,18 +5,25 @@ using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Events;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.DisplayConditions; using Artemis.UI.Screens.ProfileEditor.DisplayConditions;
using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties;
using Artemis.UI.Screens.ProfileEditor.ProfileTree; using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization;
using Artemis.UI.Screens.Sidebar.Dialogs;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
using ProfileConfigurationEventArgs = Artemis.UI.Shared.ProfileConfigurationEventArgs;
namespace Artemis.UI.Screens.ProfileEditor namespace Artemis.UI.Screens.ProfileEditor
{ {
public class ProfileEditorViewModel : Screen, IMainScreenViewModel public class ProfileEditorViewModel : MainScreenViewModel
{ {
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private readonly ISidebarVmFactory _sidebarVmFactory;
private readonly IEventAggregator _eventAggregator;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
@ -37,12 +44,14 @@ namespace Artemis.UI.Screens.ProfileEditor
IProfileService profileService, IProfileService profileService,
IDialogService dialogService, IDialogService dialogService,
ISettingsService settingsService, ISettingsService settingsService,
IMessageService messageService) IMessageService messageService,
ISidebarVmFactory sidebarVmFactory)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_profileService = profileService; _profileService = profileService;
_settingsService = settingsService; _settingsService = settingsService;
_messageService = messageService; _messageService = messageService;
_sidebarVmFactory = sidebarVmFactory;
DisplayName = "Profile Editor"; DisplayName = "Profile Editor";
DialogService = dialogService; DialogService = dialogService;
@ -60,6 +69,8 @@ namespace Artemis.UI.Screens.ProfileEditor
public IDialogService DialogService { get; } public IDialogService DialogService { get; }
public ProfileConfiguration ProfileConfiguration => _profileEditorService.SelectedProfileConfiguration;
public DisplayConditionsViewModel DisplayConditionsViewModel public DisplayConditionsViewModel DisplayConditionsViewModel
{ {
get => _displayConditionsViewModel; get => _displayConditionsViewModel;
@ -176,20 +187,96 @@ namespace Artemis.UI.Screens.ProfileEditor
_messageService.ShowMessage("Redid profile update", "UNDO", Undo); _messageService.ShowMessage("Redid profile update", "UNDO", Undo);
} }
#region Menu
public bool HasSelectedElement => _profileEditorService.SelectedProfileElement != null;
public bool CanPaste => _profileEditorService.GetCanPasteProfileElement();
public async Task ViewProperties()
{
await _sidebarVmFactory.SidebarProfileConfigurationViewModel(_profileEditorService.SelectedProfileConfiguration).ViewProperties();
}
public void DuplicateProfile()
{
ProfileConfigurationExportModel export = _profileService.ExportProfile(ProfileConfiguration);
_profileService.ImportProfile(ProfileConfiguration.Category, export, true, false, "copy");
}
public async Task DeleteProfile()
{
await _sidebarVmFactory.SidebarProfileConfigurationViewModel(_profileEditorService.SelectedProfileConfiguration).Delete();
}
public async Task ExportProfile()
{
await _sidebarVmFactory.SidebarProfileConfigurationViewModel(_profileEditorService.SelectedProfileConfiguration).Export();
}
public void Copy()
{
if (_profileEditorService.SelectedProfileElement != null)
_profileEditorService.CopyProfileElement(_profileEditorService.SelectedProfileElement);
}
public void Duplicate()
{
if (_profileEditorService.SelectedProfileElement != null)
_profileEditorService.DuplicateProfileElement(_profileEditorService.SelectedProfileElement);
}
public void Paste()
{
if (_profileEditorService.SelectedProfileElement != null && _profileEditorService.SelectedProfileElement.Parent is Folder parent)
_profileEditorService.PasteProfileElement(parent, _profileEditorService.SelectedProfileElement.Order - 1);
else
{
Folder rootFolder = _profileEditorService.SelectedProfile?.GetRootFolder();
if (rootFolder != null)
_profileEditorService.PasteProfileElement(rootFolder, rootFolder.Children.Count);
}
}
public void OpenUrl(string url)
{
Core.Utilities.OpenUrl(url);
}
public void EditMenuOpened()
{
NotifyOfPropertyChange(nameof(CanPaste));
}
#endregion
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
_profileEditorService.SelectedProfileChanged += ProfileEditorServiceOnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged;
LoadWorkspaceSettings(); LoadWorkspaceSettings();
base.OnInitialActivate(); base.OnInitialActivate();
} }
protected override void OnClose() protected override void OnClose()
{ {
_profileEditorService.SelectedProfileChanged -= ProfileEditorServiceOnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged;
SaveWorkspaceSettings(); SaveWorkspaceSettings();
_profileEditorService.ChangeSelectedProfileConfiguration(null); _profileEditorService.ChangeSelectedProfileConfiguration(null);
base.OnClose(); base.OnClose();
} }
private void ProfileEditorServiceOnSelectedProfileChanged(object sender, ProfileConfigurationEventArgs e)
{
NotifyOfPropertyChange(nameof(ProfileConfiguration));
}
private void ProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e)
{
NotifyOfPropertyChange(nameof(HasSelectedElement));
}
private void LoadWorkspaceSettings() private void LoadWorkspaceSettings()
{ {
SidePanelsWidth = _settingsService.GetSetting("ProfileEditor.SidePanelsWidth", new GridLength(385)); SidePanelsWidth = _settingsService.GetSetting("ProfileEditor.SidePanelsWidth", new GridLength(385));

View File

@ -150,14 +150,13 @@
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right" <StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
Margin="10" ZIndex="1"> Margin="10" ZIndex="1">
<Slider Margin="0,0,14,0" <Slider Orientation="Vertical"
Orientation="Vertical" HorizontalAlignment="Center"
Margin="0 10"
Height="120"
Minimum="10" Minimum="10"
Maximum="250" Maximum="250"
Height="100" Value="{Binding PanZoomViewModel.ZoomPercentage}" />
FocusVisualStyle="{x:Null}"
Value="{Binding PanZoomViewModel.ZoomPercentage}"
Style="{StaticResource MaterialDesignDiscreteSlider}" />
<Button Command="{s:Action ResetZoomAndPan}" <Button Command="{s:Action ResetZoomAndPan}"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}" Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
HorizontalAlignment="Right" HorizontalAlignment="Right"

View File

@ -1,4 +1,4 @@
<mde:MaterialWindow x:Class="Artemis.UI.Screens.RootView" <mde:MaterialWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -8,6 +8,7 @@
xmlns:screens="clr-namespace:Artemis.UI.Screens" xmlns:screens="clr-namespace:Artemis.UI.Screens"
xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:Shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" x:Class="Artemis.UI.Screens.RootView"
mc:Ignorable="d" mc:Ignorable="d"
FadeContentIfInactive="False" FadeContentIfInactive="False"
Icon="/Resources/Images/Logo/bow.ico" Icon="/Resources/Images/Logo/bow.ico"
@ -24,7 +25,10 @@
MouseDown="{s:Action WindowMouseDown}" MouseDown="{s:Action WindowMouseDown}"
MouseUp="{s:Action WindowMouseUp}" MouseUp="{s:Action WindowMouseUp}"
d:DesignHeight="640" d:DesignWidth="1200" d:DesignHeight="640" d:DesignWidth="1200"
d:DataContext="{d:DesignInstance screens:RootViewModel}"> d:DataContext="{d:DesignInstance {x:Type screens:RootViewModel}}">
<mde:MaterialWindow.Resources>
<Shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
</mde:MaterialWindow.Resources>
<materialDesign:DialogHost IsTabStop="False" Focusable="False" Identifier="RootDialog" DialogTheme="Inherit" SnackbarMessageQueue="{Binding MainMessageQueue}"> <materialDesign:DialogHost IsTabStop="False" Focusable="False" Identifier="RootDialog" DialogTheme="Inherit" SnackbarMessageQueue="{Binding MainMessageQueue}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -42,30 +46,13 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<materialDesign:ColorZone Grid.Row="0" Mode="PrimaryMid" DockPanel.Dock="Top" DockPanel.ZIndex="1" Height="42"> <materialDesign:ColorZone Grid.Row="0"
<Grid> Mode="PrimaryMid"
<Grid.ColumnDefinitions> DockPanel.Dock="Top"
<ColumnDefinition Width="*" /> Panel.ZIndex="1"
<ColumnDefinition Width="Auto" /> Height="48"
</Grid.ColumnDefinitions> Visibility="{Binding SidebarViewModel.SelectedScreen.HeaderViewModel, Converter={StaticResource NullToVisibilityConverter}}" >
<TextBlock Grid.Column="0" <ContentControl s:View.Model="{Binding SidebarViewModel.SelectedScreen.HeaderViewModel}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
Text="{Binding SidebarViewModel.SelectedScreen.DisplayName}"
VerticalAlignment="Center"
FontSize="20"
Margin="15 0" />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Text="{Binding FrameTime}" VerticalAlignment="Center" FontSize="14" Margin="10 0" ToolTip="The time the last frame took to render" />
<!-- Bug: materialDesign:RippleAssist.RippleOnTop doesn't look as nice but otherwise it doesn't work at all, not sure why -->
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
VerticalAlignment="Center"
ToolTip="Open debugger"
Command="{s:Action ShowDebugger}"
materialDesign:RippleAssist.RippleOnTop="True">
<materialDesign:PackIcon Kind="Matrix" />
</Button>
</StackPanel>
</Grid>
</materialDesign:ColorZone> </materialDesign:ColorZone>
<ContentControl Grid.Row="1" <ContentControl Grid.Row="1"

View File

@ -1,8 +1,6 @@
using System; using System;
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
@ -21,19 +19,17 @@ using Constants = Artemis.Core.Constants;
namespace Artemis.UI.Screens namespace Artemis.UI.Screens
{ {
public sealed class RootViewModel : Conductor<Screen>, IDisposable public sealed class RootViewModel : Conductor<Screen>
{ {
private readonly IRegistrationService _builtInRegistrationService; private readonly IRegistrationService _builtInRegistrationService;
private readonly ICoreService _coreService;
private readonly IDebugService _debugService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Timer _frameTimeUpdateTimer;
private readonly IKernel _kernel; private readonly IKernel _kernel;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
private readonly PluginSetting<WindowSize> _windowSize; private readonly PluginSetting<WindowSize> _windowSize;
private string _frameTime;
private bool _lostFocus; private bool _lostFocus;
private ISnackbarMessageQueue _mainMessageQueue; private ISnackbarMessageQueue _mainMessageQueue;
private MaterialWindow _window; private MaterialWindow _window;
@ -43,9 +39,7 @@ namespace Artemis.UI.Screens
IKernel kernel, IKernel kernel,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
ISettingsService settingsService, ISettingsService settingsService,
ICoreService coreService,
IWindowManager windowManager, IWindowManager windowManager,
IDebugService debugService,
IRegistrationService builtInRegistrationService, IRegistrationService builtInRegistrationService,
IMessageService messageService, IMessageService messageService,
SidebarViewModel sidebarViewModel) SidebarViewModel sidebarViewModel)
@ -53,12 +47,9 @@ namespace Artemis.UI.Screens
_kernel = kernel; _kernel = kernel;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_settingsService = settingsService; _settingsService = settingsService;
_coreService = coreService;
_windowManager = windowManager; _windowManager = windowManager;
_debugService = debugService;
_builtInRegistrationService = builtInRegistrationService; _builtInRegistrationService = builtInRegistrationService;
_messageService = messageService; _messageService = messageService;
_frameTimeUpdateTimer = new Timer(500);
_windowSize = _settingsService.GetSetting<WindowSize>("UI.RootWindowSize"); _windowSize = _settingsService.GetSetting<WindowSize>("UI.RootWindowSize");
SidebarWidth = _settingsService.GetSetting("UI.SidebarWidth", new GridLength(240)); SidebarWidth = _settingsService.GetSetting("UI.SidebarWidth", new GridLength(240));
@ -84,11 +75,6 @@ namespace Artemis.UI.Screens
set => SetAndNotify(ref _windowTitle, value); set => SetAndNotify(ref _windowTitle, value);
} }
public string FrameTime
{
get => _frameTime;
set => SetAndNotify(ref _frameTime, value);
}
public void WindowDeactivated() public void WindowDeactivated()
{ {
@ -109,11 +95,6 @@ namespace Artemis.UI.Screens
_eventAggregator.Publish(new MainWindowFocusChangedEvent(true)); _eventAggregator.Publish(new MainWindowFocusChangedEvent(true));
} }
public void ShowDebugger()
{
_debugService.ShowDebugger();
}
public void WindowKeyDown(object sender, KeyEventArgs e) public void WindowKeyDown(object sender, KeyEventArgs e)
{ {
_eventAggregator.Publish(new MainWindowKeyEvent(sender, true, e)); _eventAggregator.Publish(new MainWindowKeyEvent(sender, true, e));
@ -134,22 +115,6 @@ namespace Artemis.UI.Screens
_eventAggregator.Publish(new MainWindowMouseEvent(sender, false, e)); _eventAggregator.Publish(new MainWindowMouseEvent(sender, false, e));
} }
private void UpdateFrameTime()
{
FrameTime = $"Frame time: {_coreService.FrameTime.TotalMilliseconds:F2} ms";
}
private void OnFrameTimeUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
{
UpdateFrameTime();
}
/// <inheritdoc />
public void Dispose()
{
_frameTimeUpdateTimer?.Dispose();
}
private void SidebarViewModelOnSelectedScreenChanged(object? sender, EventArgs e) private void SidebarViewModelOnSelectedScreenChanged(object? sender, EventArgs e)
{ {
ActiveItem = SidebarViewModel.SelectedScreen; ActiveItem = SidebarViewModel.SelectedScreen;
@ -181,8 +146,6 @@ namespace Artemis.UI.Screens
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
MainMessageQueue = _messageService.MainMessageQueue; MainMessageQueue = _messageService.MainMessageQueue;
UpdateFrameTime();
SidebarViewModel.SelectedScreenChanged += SidebarViewModelOnSelectedScreenChanged; SidebarViewModel.SelectedScreenChanged += SidebarViewModelOnSelectedScreenChanged;
ActiveItem = SidebarViewModel.SelectedScreen; ActiveItem = SidebarViewModel.SelectedScreen;
@ -190,9 +153,6 @@ namespace Artemis.UI.Screens
_builtInRegistrationService.RegisterBuiltInDataModelInputs(); _builtInRegistrationService.RegisterBuiltInDataModelInputs();
_builtInRegistrationService.RegisterBuiltInPropertyEditors(); _builtInRegistrationService.RegisterBuiltInPropertyEditors();
_frameTimeUpdateTimer.Elapsed += OnFrameTimeUpdateTimerOnElapsed;
_frameTimeUpdateTimer.Start();
_window = (MaterialWindow) View; _window = (MaterialWindow) View;
PluginSetting<bool> setupWizardCompleted = _settingsService.GetSetting("UI.SetupWizardCompleted", false); PluginSetting<bool> setupWizardCompleted = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
@ -209,7 +169,6 @@ namespace Artemis.UI.Screens
Keyboard.ClearFocus(); Keyboard.ClearFocus();
MainMessageQueue = null; MainMessageQueue = null;
_frameTimeUpdateTimer.Stop();
SidebarViewModel.SelectedScreenChanged -= SidebarViewModelOnSelectedScreenChanged; SidebarViewModel.SelectedScreenChanged -= SidebarViewModelOnSelectedScreenChanged;
SidebarWidth.Save(); SidebarWidth.Save();
@ -217,8 +176,6 @@ namespace Artemis.UI.Screens
_windowSize.Value.ApplyFromWindow(_window); _windowSize.Value.ApplyFromWindow(_window);
_windowSize.Save(); _windowSize.Save();
_frameTimeUpdateTimer.Elapsed -= OnFrameTimeUpdateTimerOnElapsed;
// Lets force the GC to run after closing the window so it is obvious to users watching task manager // Lets force the GC to run after closing the window so it is obvious to users watching task manager
// that closing the UI will decrease the memory footprint of the application. // that closing the UI will decrease the memory footprint of the application.
Task.Run(async () => Task.Run(async () =>

View File

@ -0,0 +1,24 @@
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Scripting
{
public class ScriptsDialogViewModel : Conductor<ScriptConfigurationViewModel>.Collection.OneActive
{
public ScriptsDialogViewModel(Profile profile)
{
}
public ScriptsDialogViewModel(Layer layer)
{
}
public ScriptsDialogViewModel(ILayerProperty layerProperty)
{
}
}
public class ScriptConfigurationViewModel : Screen
{
}
}

View File

@ -0,0 +1,33 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.SettingsTabsView"
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:local="clr-namespace:Artemis.UI.Screens.Settings"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SettingsTabsViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<TabControl Style="{StaticResource MaterialDesignAppBarTabControl}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="DisplayName">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"
TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl>

View File

@ -0,0 +1,27 @@
using Artemis.UI.Screens.Settings.Tabs.About;
using Artemis.UI.Screens.Settings.Tabs.Devices;
using Artemis.UI.Screens.Settings.Tabs.General;
using Artemis.UI.Screens.Settings.Tabs.Plugins;
using Stylet;
namespace Artemis.UI.Screens.Settings
{
public class SettingsTabsViewModel : Conductor<Screen>.Collection.OneActive
{
public SettingsTabsViewModel(
GeneralSettingsTabViewModel generalSettingsTabViewModel,
PluginSettingsTabViewModel pluginSettingsTabViewModel,
DeviceSettingsTabViewModel deviceSettingsTabViewModel,
AboutTabViewModel aboutTabViewModel)
{
DisplayName = "Settings";
Items.Add(generalSettingsTabViewModel);
Items.Add(pluginSettingsTabViewModel);
Items.Add(deviceSettingsTabViewModel);
Items.Add(aboutTabViewModel);
ActiveItem = generalSettingsTabViewModel;
}
}
}

View File

@ -8,26 +8,5 @@
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance settings:SettingsViewModel}" d:DataContext="{d:DesignInstance settings:SettingsViewModel}"
d:DesignHeight="600" d:DesignWidth="600"> d:DesignHeight="600" d:DesignWidth="600">
<UserControl.Resources> <ContentControl s:View.Model="{Binding ActiveItem}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<TabControl Style="{StaticResource MaterialDesignTabControl}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="DisplayName">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"
TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl> </UserControl>

View File

@ -1,27 +1,17 @@
using Artemis.UI.Screens.Settings.Tabs.About; using Stylet;
using Artemis.UI.Screens.Settings.Tabs.Devices;
using Artemis.UI.Screens.Settings.Tabs.General;
using Artemis.UI.Screens.Settings.Tabs.Plugins;
using Stylet;
namespace Artemis.UI.Screens.Settings namespace Artemis.UI.Screens.Settings
{ {
public class SettingsViewModel : Conductor<Screen>.Collection.OneActive, IMainScreenViewModel public class SettingsViewModel : MainScreenViewModel
{ {
public SettingsViewModel( public SettingsViewModel(SettingsTabsViewModel settingsTabsViewModel)
GeneralSettingsTabViewModel generalSettingsTabViewModel,
PluginSettingsTabViewModel pluginSettingsTabViewModel,
DeviceSettingsTabViewModel deviceSettingsTabViewModel,
AboutTabViewModel aboutTabViewModel)
{ {
DisplayName = "Settings"; DisplayName = "Settings";
Items.Add(generalSettingsTabViewModel); settingsTabsViewModel.ConductWith(this);
Items.Add(pluginSettingsTabViewModel); ActiveItem = settingsTabsViewModel;
Items.Add(deviceSettingsTabViewModel);
Items.Add(aboutTabViewModel);
ActiveItem = generalSettingsTabViewModel;
} }
public SettingsTabsViewModel ActiveItem { get; }
} }
} }

View File

@ -70,7 +70,7 @@
<Separator /> <Separator />
<MenuItem Header="Delete" Command="{s:Action Delete}" InputGestureText="Del"> <MenuItem Header="Delete" Command="{s:Action Delete}" InputGestureText="Del">
<MenuItem.Icon> <MenuItem.Icon>
<materialDesign:PackIcon Kind="TrashCan" /> <materialDesign:PackIcon Kind="Trash" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>

View File

@ -1,16 +1,15 @@
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
using Ninject; using Ninject;
using Stylet;
namespace Artemis.UI.Screens.Sidebar namespace Artemis.UI.Screens.Sidebar
{ {
public class SidebarScreenViewModel<T> : SidebarScreenViewModel where T : Screen public class SidebarScreenViewModel<T> : SidebarScreenViewModel where T : MainScreenViewModel
{ {
public SidebarScreenViewModel(PackIconKind icon, string displayName) : base(icon, displayName) public SidebarScreenViewModel(PackIconKind icon, string displayName) : base(icon, displayName)
{ {
} }
public override Screen CreateInstance(IKernel kernel) public override MainScreenViewModel CreateInstance(IKernel kernel)
{ {
return kernel.Get<T>(); return kernel.Get<T>();
} }
@ -24,8 +23,9 @@ namespace Artemis.UI.Screens.Sidebar
DisplayName = displayName; DisplayName = displayName;
} }
public abstract Screen CreateInstance(IKernel kernel);
public PackIconKind Icon { get; } public PackIconKind Icon { get; }
public string DisplayName { get; } public string DisplayName { get; }
public abstract MainScreenViewModel CreateInstance(IKernel kernel);
} }
} }

View File

@ -30,9 +30,9 @@ namespace Artemis.UI.Screens.Sidebar
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private SidebarScreenViewModel _selectedSidebarScreen;
private ArtemisDevice _headerDevice; private ArtemisDevice _headerDevice;
private Screen _selectedScreen; private SidebarScreenViewModel _selectedSidebarScreen;
private MainScreenViewModel _selectedScreen;
private readonly SidebarScreenViewModel<ProfileEditorViewModel> _profileEditor; private readonly SidebarScreenViewModel<ProfileEditorViewModel> _profileEditor;
private readonly DefaultDropHandler _defaultDropHandler; private readonly DefaultDropHandler _defaultDropHandler;
@ -80,7 +80,7 @@ namespace Artemis.UI.Screens.Sidebar
public BindableCollection<SidebarScreenViewModel> SidebarScreens { get; } public BindableCollection<SidebarScreenViewModel> SidebarScreens { get; }
public Screen SelectedScreen public MainScreenViewModel SelectedScreen
{ {
get => _selectedScreen; get => _selectedScreen;
private set => SetAndNotify(ref _selectedScreen, value); private set => SetAndNotify(ref _selectedScreen, value);

View File

@ -21,7 +21,7 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="16"> <Grid Margin="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100" /> <ColumnDefinition Width="*" MinWidth="100" />
<ColumnDefinition Width="5" /> <ColumnDefinition Width="5" />
@ -220,7 +220,7 @@
</materialDesign:Card> </materialDesign:Card>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0, 0, 15, 15"> <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0 0 15 15">
<Button Command="{s:Action AutoArrange}" <Button Command="{s:Action AutoArrange}"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}" Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -228,15 +228,14 @@
VerticalAlignment="Bottom"> VerticalAlignment="Bottom">
<materialDesign:PackIcon Kind="Wand" Height="24" Width="24" /> <materialDesign:PackIcon Kind="Wand" Height="24" Width="24" />
</Button> </Button>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical" Margin="10 0 0 0">
<Slider Margin="0,0,14,0" <Slider Orientation="Vertical"
Orientation="Vertical" HorizontalAlignment="Center"
Margin="0 10"
Height="120"
Minimum="10" Minimum="10"
Maximum="400" Maximum="400"
Height="100" Value="{Binding PanZoomViewModel.ZoomPercentage}" />
FocusVisualStyle="{x:Null}"
Value="{Binding PanZoomViewModel.ZoomPercentage}"
Style="{StaticResource MaterialDesignDiscreteSlider}" />
<Button Command="{s:Action ResetZoomAndPan}" <Button Command="{s:Action ResetZoomAndPan}"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}" Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -317,7 +316,7 @@
</materialDesign:DialogHost> </materialDesign:DialogHost>
</materialDesign:Card> </materialDesign:Card>
<TextBlock Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignCaptionTextBlock}" Margin="0,5,0,0"> <TextBlock Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignCaptionTextBlock}" Margin="0 5 0 -5">
<InlineUIContainer> <InlineUIContainer>
<materialDesign:PackIcon Kind="Keyboard" Margin="0 0 0 -3" /> <materialDesign:PackIcon Kind="Keyboard" Margin="0 0 0 -3" />
</InlineUIContainer> </InlineUIContainer>

View File

@ -22,7 +22,7 @@ using MouseButton = System.Windows.Input.MouseButton;
namespace Artemis.UI.Screens.SurfaceEditor namespace Artemis.UI.Screens.SurfaceEditor
{ {
public class SurfaceEditorViewModel : Screen, IMainScreenViewModel public class SurfaceEditorViewModel : MainScreenViewModel
{ {
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;

View File

@ -3,7 +3,7 @@ using Stylet;
namespace Artemis.UI.Screens.Workshop namespace Artemis.UI.Screens.Workshop
{ {
public class WorkshopViewModel : Screen, IMainScreenViewModel public class WorkshopViewModel : MainScreenViewModel
{ {
private Color _testColor; private Color _testColor;
private bool _testPopupOpen; private bool _testPopupOpen;

View File

@ -53,11 +53,11 @@
}, },
"MaterialDesignThemes": { "MaterialDesignThemes": {
"type": "Direct", "type": "Direct",
"requested": "[4.0.0, )", "requested": "[4.1.0, )",
"resolved": "4.0.0", "resolved": "4.1.0",
"contentHash": "+n5oWHuRiYL/gUw2XfQHCRZqHtU8KbrdurgU0IcO98Zsyhw4BvggodfXY8veRtbjjmM9EJ/sG2yKBrgPOGX4JQ==", "contentHash": "WqrO9AbtdE4pLPtDk/C5BZRnkgWFwVGyUHWj7tRJrgnKl089DEobVXBCLeqp2mkgBeFHj4Xe3AfWyhmlnO6AZA==",
"dependencies": { "dependencies": {
"MaterialDesignColors": "2.0.0" "MaterialDesignColors": "2.0.1"
} }
}, },
"Microsoft.Toolkit.Uwp.Notifications": { "Microsoft.Toolkit.Uwp.Notifications": {
@ -262,8 +262,8 @@
}, },
"MaterialDesignColors": { "MaterialDesignColors": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.0.0", "resolved": "2.0.1",
"contentHash": "+JoghC3QRK0u9Wul1To1ORjcfTbFTVzFPjJ02H7VREOdNzIIn427e8G9gP9hXu9pm1r2OneLnoCG/lTma5cG2w==" "contentHash": "Azl8nN23SD6QPE0PdsfpKiIqWTvH7rzXwgXPiFSEt91NFOrwB5cx3iq/sbINWMZunhXJ32+jVUHiV03B8eJbZw=="
}, },
"McMaster.NETCore.Plugins": { "McMaster.NETCore.Plugins": {
"type": "Transitive", "type": "Transitive",
@ -1489,7 +1489,7 @@
"Ben.Demystifier": "0.3.0", "Ben.Demystifier": "0.3.0",
"Humanizer.Core": "2.8.26", "Humanizer.Core": "2.8.26",
"MaterialDesignExtensions": "3.3.0", "MaterialDesignExtensions": "3.3.0",
"MaterialDesignThemes": "4.0.0", "MaterialDesignThemes": "4.1.0",
"Microsoft.Xaml.Behaviors.Wpf": "1.1.31", "Microsoft.Xaml.Behaviors.Wpf": "1.1.31",
"Ninject": "3.3.4", "Ninject": "3.3.4",
"Ninject.Extensions.Conventions": "3.3.0", "Ninject.Extensions.Conventions": "3.3.0",