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-12 22:56:25 +02:00
commit 7b9edb9e49
66 changed files with 587 additions and 291 deletions

View File

@ -57,6 +57,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Clayerbrushes_005Cinternal/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Clayerbrushes_005Cinternal/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Clayereffects_005Cinternal/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Clayereffects_005Cinternal/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cmodules_005Cactivationrequirements/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cmodules_005Cactivationrequirements/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cmodules_005Cattributes/@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/@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>

View File

@ -0,0 +1,20 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Provides data about data model path related events
/// </summary>
public class DataModelPathEventArgs : EventArgs
{
internal DataModelPathEventArgs(DataModelPath dataModelPath)
{
DataModelPath = dataModelPath;
}
/// <summary>
/// Gets the data model path this event is related to
/// </summary>
public DataModelPath DataModelPath { get; }
}
}

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
namespace Artemis.Core namespace Artemis.Core
@ -175,6 +175,8 @@ namespace Artemis.Core
internal void Invalidate() internal void Invalidate()
{ {
Target?.RemoveDataModelPath(this);
foreach (DataModelPathSegment dataModelPathSegment in _segments) foreach (DataModelPathSegment dataModelPathSegment in _segments)
dataModelPathSegment.Dispose(); dataModelPathSegment.Dispose();
_segments.Clear(); _segments.Clear();
@ -187,11 +189,11 @@ namespace Artemis.Core
internal void Initialize() internal void Initialize()
{ {
Invalidate();
if (Target == null) if (Target == null)
return; return;
Target.AddDataModelPath(this);
DataModelPathSegment startSegment = new(this, "target", "target"); DataModelPathSegment startSegment = new(this, "target", "target");
startSegment.Node = _segments.AddFirst(startSegment); startSegment.Node = _segments.AddFirst(startSegment);
@ -260,7 +262,7 @@ namespace Artemis.Core
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
} }
#region IDisposable #region IDisposable
/// <summary> /// <summary>
@ -330,6 +332,7 @@ namespace Artemis.Core
if (e.Registration.DataModel.Module.Id != Entity.DataModelId) if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
return; return;
Invalidate();
Target = e.Registration.DataModel; Target = e.Registration.DataModel;
Initialize(); Initialize();
} }
@ -339,8 +342,8 @@ namespace Artemis.Core
if (e.Registration.DataModel.Module.Id != Entity.DataModelId) if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
return; return;
Target = null;
Invalidate(); Invalidate();
Target = null;
} }
#endregion #endregion

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Humanizer; using Humanizer;
namespace Artemis.Core namespace Artemis.Core
@ -301,7 +301,7 @@ namespace Artemis.Core
private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e)
{ {
if (e.DynamicChild.BaseValue == _dynamicDataModel) if (e.DynamicChild.BaseValue == _dynamicDataModel)
DataModelPath.Initialize(); DataModelPath.Invalidate();
} }
#endregion #endregion

View File

@ -1,4 +1,4 @@
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -5,6 +5,9 @@ using Artemis.Storage.Entities.Profile;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a category containing <see cref="ProfileConfigurations" />
/// </summary>
public class ProfileCategory : CorePropertyChanged, IStorageModel public class ProfileCategory : CorePropertyChanged, IStorageModel
{ {
private readonly List<ProfileConfiguration> _profileConfigurations = new(); private readonly List<ProfileConfiguration> _profileConfigurations = new();

View File

@ -59,6 +59,8 @@ namespace Artemis.Core
foreach (BaseLayerEffect baseLayerEffect in LayerEffects) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose(); baseLayerEffect.Dispose();
DisplayCondition?.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -1,13 +1,18 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
namespace Artemis.Core namespace Artemis.Core
{ {
public class ProfileConfiguration : CorePropertyChanged, IStorageModel /// <summary>
/// Represents the configuration of a profile, contained in a <see cref="ProfileCategory" />
/// </summary>
public class ProfileConfiguration : CorePropertyChanged, IStorageModel, IDisposable
{ {
private ProfileCategory _category; private ProfileCategory _category;
private bool _disposed;
private bool _isMissingModule; private bool _isMissingModule;
private bool _isSuspended; private bool _isSuspended;
@ -140,11 +145,17 @@ namespace Artemis.Core
/// </summary> /// </summary>
public void Update() public void Update()
{ {
if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration");
ActivationConditionMet = ActivationCondition == null || ActivationCondition.Evaluate(); ActivationConditionMet = ActivationCondition == null || ActivationCondition.Evaluate();
} }
public bool ShouldBeActive(bool includeActivationCondition) public bool ShouldBeActive(bool includeActivationCondition)
{ {
if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration");
if (Category.IsSuspended || IsSuspended || IsMissingModule) if (Category.IsSuspended || IsSuspended || IsMissingModule)
return false; return false;
@ -161,15 +172,32 @@ namespace Artemis.Core
internal void LoadModules(List<Module> enabledModules) internal void LoadModules(List<Module> enabledModules)
{ {
if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration");
Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId); Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId);
IsMissingModule = Module == null && Entity.ModuleId != null; IsMissingModule = Module == null && Entity.ModuleId != null;
} }
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
ActivationCondition?.Dispose();
}
#endregion
#region Implementation of IStorageModel #region Implementation of IStorageModel
/// <inheritdoc /> /// <inheritdoc />
public void Load() public void Load()
{ {
if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration");
Name = Entity.Name; Name = Entity.Name;
IsSuspended = Entity.IsSuspended; IsSuspended = Entity.IsSuspended;
ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour; ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour;
@ -185,6 +213,9 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public void Save() public void Save()
{ {
if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration");
Entity.Name = Name; Entity.Name = Name;
Entity.IsSuspended = IsSuspended; Entity.IsSuspended = IsSuspended;
Entity.ActivationBehaviour = (int) ActivationBehaviour; Entity.ActivationBehaviour = (int) ActivationBehaviour;
@ -199,9 +230,7 @@ namespace Artemis.Core
Entity.ActivationCondition = ActivationCondition.Entity; Entity.ActivationCondition = ActivationCondition.Entity;
} }
else else
{
Entity.ActivationCondition = null; Entity.ActivationCondition = null;
}
if (!IsMissingModule) if (!IsMissingModule)
Entity.ModuleId = Module?.Id; Entity.ModuleId = Module?.Id;

View File

@ -549,11 +549,6 @@ namespace Artemis.Core
else else
LogicalLayout = DeviceEntity.LogicalLayout; LogicalLayout = DeviceEntity.LogicalLayout;
} }
public void ClearLayout()
{
// TODO
}
} }
/// <summary> /// <summary>

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Artemis.Core.Services;
using Ninject;
namespace Artemis.Core.Modules namespace Artemis.Core.Modules
{ {
@ -11,6 +13,8 @@ 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>
@ -18,6 +22,14 @@ namespace Artemis.Core.Modules
/// <param name="location">The location of where the process must be running from (optional)</param> /// <param name="location">The location of where the process must be running from (optional)</param>
public ProcessActivationRequirement(string? processName, string? location = null) public ProcessActivationRequirement(string? processName, string? location = null)
{ {
if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location))
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;
} }
@ -38,10 +50,13 @@ namespace Artemis.Core.Modules
if (ProcessName == null && Location == null) if (ProcessName == null && Location == null)
return false; return false;
IEnumerable<Process> processes = ProcessName != null ? Process.GetProcessesByName(ProcessName).Where(p => !p.HasExited) : Process.GetProcesses().Where(p => !p.HasExited); IEnumerable<Process> processes = _processMonitorService.GetRunningProcesses();
return Location != null if (ProcessName != null)
? processes.Any(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.CurrentCultureIgnoreCase)) processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase));
: processes.Any(); if (Location != null)
processes = processes.Where(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.InvariantCultureIgnoreCase));
return processes.Any();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Artemis.Core.DataModelExpansions namespace Artemis.Core.Modules
{ {
/// <summary> /// <summary>
/// Represents an attribute that marks a data model property to be ignored by the UI /// Represents an attribute that marks a data model property to be ignored by the UI

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Artemis.Core.DataModelExpansions namespace Artemis.Core.Modules
{ {
/// <summary> /// <summary>
/// Represents an attribute that describes a data model property /// Represents an attribute that describes a data model property

View File

@ -1,21 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.Modules;
using Humanizer; using Humanizer;
using Newtonsoft.Json; using Newtonsoft.Json;
using Module = Artemis.Core.Modules.Module;
namespace Artemis.Core.DataModelExpansions namespace Artemis.Core.Modules
{ {
/// <summary> /// <summary>
/// Represents a data model that contains information on a game/application etc. /// Represents a data model that contains information on a game/application etc.
/// </summary> /// </summary>
public abstract class DataModel public abstract class DataModel
{ {
private readonly HashSet<string> _activePathsHashSet = new();
private readonly List<DataModelPath> _activePaths = new();
private readonly Dictionary<string, DynamicChild> _dynamicChildren = new(); private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
/// <summary> /// <summary>
@ -54,42 +54,18 @@ namespace Artemis.Core.DataModelExpansions
[DataModelIgnore] [DataModelIgnore]
public ReadOnlyDictionary<string, DynamicChild> DynamicChildren => new(_dynamicChildren); public ReadOnlyDictionary<string, DynamicChild> DynamicChildren => new(_dynamicChildren);
/// <summary>
/// Gets a read-only list of <see cref="DataModelPath" />s targeting this data model
/// </summary>
public ReadOnlyCollection<DataModelPath> ActivePaths => _activePaths.AsReadOnly();
/// <summary> /// <summary>
/// Returns a read-only collection of all properties in this datamodel that are to be ignored /// Returns a read-only collection of all properties in this datamodel that are to be ignored
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties() public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
{ {
if (Module is Module module) return Module.HiddenProperties;
return module.HiddenProperties;
return new List<PropertyInfo>().AsReadOnly();
}
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
/// <summary>
/// Invokes the <see cref="DynamicChildAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
{
DynamicChildAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DynamicChildRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
{
DynamicChildRemoved?.Invoke(this, e);
} }
#region Dynamic children #region Dynamic children
@ -277,5 +253,108 @@ namespace Artemis.Core.DataModelExpansions
} }
#endregion #endregion
#region Paths
/// <summary>
/// Determines whether the provided dot-separated path is in use
/// </summary>
/// <param name="path">The path to check per example: <c>MyDataModelChild.MyDataModelProperty</c></param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
internal bool IsPropertyInUse(string path, bool includeChildren)
{
path = path.ToUpperInvariant();
return includeChildren
? _activePathsHashSet.Any(p => p.StartsWith(path, StringComparison.Ordinal))
: _activePathsHashSet.Contains(path);
}
internal void AddDataModelPath(DataModelPath path)
{
if (_activePaths.Contains(path))
return;
_activePaths.Add(path);
// Add to the hashset if this is the first path pointing
string hashPath = path.Path.ToUpperInvariant();
if (!_activePathsHashSet.Contains(hashPath))
_activePathsHashSet.Add(hashPath);
OnActivePathAdded(new DataModelPathEventArgs(path));
}
internal void RemoveDataModelPath(DataModelPath path)
{
if (!_activePaths.Remove(path))
return;
// Remove from the hashset if this was the last path pointing there
if (_activePaths.All(p => p.Path != path.Path))
_activePathsHashSet.Remove(path.Path.ToUpperInvariant());
OnActivePathRemoved(new DataModelPathEventArgs(path));
}
#endregion
#region Events
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DataModelPathEventArgs>? ActivePathAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DataModelPathEventArgs>? ActivePathRemoved;
/// <summary>
/// Invokes the <see cref="DynamicChildAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
{
DynamicChildAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DynamicChildRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
{
DynamicChildRemoved?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="ActivePathAdded" /> event
/// </summary>
protected virtual void OnActivePathAdded(DataModelPathEventArgs e)
{
ActivePathAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="ActivePathRemoved" /> event
/// </summary>
protected virtual void OnActivePathRemoved(DataModelPathEventArgs e)
{
ActivePathRemoved?.Invoke(this, e);
}
#endregion
} }
} }

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Artemis.Core.DataModelExpansions namespace Artemis.Core.Modules
{ {
/// <summary> /// <summary>
/// Represents a dynamic child value with its property attribute /// Represents a dynamic child value with its property attribute

View File

@ -5,7 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Artemis.Core.DataModelExpansions; using System.Text;
namespace Artemis.Core.Modules namespace Artemis.Core.Modules
{ {
@ -25,7 +25,8 @@ namespace Artemis.Core.Modules
} }
/// <summary> /// <summary>
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC) /// Hide the provided property using a lambda expression, e.g.
/// <c>HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </summary> /// </summary>
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param> /// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda) public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
@ -36,8 +37,8 @@ namespace Artemis.Core.Modules
} }
/// <summary> /// <summary>
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm => /// Stop hiding the provided property using a lambda expression, e.g.
/// dm.TimeDataModel.CurrentTimeUTC) /// <c>ShowProperty(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </summary> /// </summary>
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param> /// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda) public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
@ -46,6 +47,36 @@ namespace Artemis.Core.Modules
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo)); HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
} }
/// <summary>
/// Determines whether the provided dot-separated path is actively being used by Artemis
/// <para>Note: <see cref="IsPropertyInUse" /> is slightly faster but string-based.</para>
/// </summary>
/// <param name="propertyLambda">
/// The path to check per example: <c>IsPropertyInUse(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
public bool IsPropertyInUse<TProperty>(Expression<Func<T, TProperty>> propertyLambda, bool includeChildren)
{
string path = GetMemberPath((MemberExpression) propertyLambda.Body);
return IsPropertyInUse(path, includeChildren);
}
/// <summary>
/// Determines whether the provided dot-separated path is actively being used by Artemis
/// </summary>
/// <param name="path">The path to check per example: <c>MyDataModelChild.MyDataModelProperty</c></param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
public bool IsPropertyInUse(string path, bool includeChildren)
{
return DataModel.IsPropertyInUse(path, includeChildren);
}
internal override void InternalEnable() internal override void InternalEnable()
{ {
DataModel = Activator.CreateInstance<T>(); DataModel = Activator.CreateInstance<T>();
@ -59,6 +90,20 @@ namespace Artemis.Core.Modules
Deactivate(true); Deactivate(true);
base.InternalDisable(); base.InternalDisable();
} }
private static string GetMemberPath(MemberExpression? me)
{
StringBuilder builder = new();
while (me != null)
{
builder.Insert(0, me.Member.Name);
me = me.Expression as MemberExpression;
if (me != null)
builder.Insert(0, ".");
}
return builder.ToString();
}
} }
/// <summary> /// <summary>
@ -78,7 +123,7 @@ namespace Artemis.Core.Modules
/// Gets a read only collection of default profile paths /// Gets a read only collection of default profile paths
/// </summary> /// </summary>
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly(); public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly();
/// <summary> /// <summary>
/// A list of activation requirements /// A list of activation requirements
/// <para> /// <para>

View File

@ -22,6 +22,8 @@ namespace Artemis.Core
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action, string? name) internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action, string? name)
{ {
if (CoreService.Kernel == null)
throw new ArtemisCoreException("Cannot create a TimedUpdateRegistration before initializing the Core");
_logger = CoreService.Kernel.Get<ILogger>(); _logger = CoreService.Kernel.Get<ILogger>();
Feature = feature; Feature = feature;
@ -37,6 +39,8 @@ namespace Artemis.Core
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction, string? name) internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction, string? name)
{ {
if (CoreService.Kernel == null)
throw new ArtemisCoreException("Cannot create a TimedUpdateRegistration before initializing the Core");
_logger = CoreService.Kernel.Get<ILogger>(); _logger = CoreService.Kernel.Get<ILogger>();
Feature = feature; Feature = feature;

View File

@ -4,7 +4,6 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;
using Artemis.Storage; using Artemis.Storage;
using HidSharp; using HidSharp;
@ -22,7 +21,7 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
internal class CoreService : ICoreService internal class CoreService : ICoreService
{ {
internal static IKernel Kernel = null!; internal static IKernel? Kernel;
private readonly Stopwatch _frameStopWatch; private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -201,7 +200,7 @@ namespace Artemis.Core.Services
Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version; Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
_logger.Debug("Forcing plugins to use HidSharp {hidSharpVersion}", hidSharpVersion); _logger.Debug("Forcing plugins to use HidSharp {hidSharpVersion}", hidSharpVersion);
DeserializationLogger.Initialize(Kernel); DeserializationLogger.Initialize(Kernel!);
// Initialize the services // Initialize the services
_pluginManagementService.CopyBuiltInPlugins(); _pluginManagementService.CopyBuiltInPlugins();

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
namespace Artemis.Core.Services namespace Artemis.Core.Services

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core.Services namespace Artemis.Core.Services
{ {

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Services.Models; using Artemis.Core.Services.Models;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
@ -345,6 +346,13 @@ namespace Artemis.Core.Services
return layout; return layout;
} }
if (device.DisableDefaultLayout)
{
layout = null;
ApplyDeviceLayout(device, layout);
return null;
}
// Look for a layout provided by the plugin // Look for a layout provided by the plugin
layout = device.DeviceProvider.LoadLayout(device); layout = device.DeviceProvider.LoadLayout(device);
if (layout.IsValid) if (layout.IsValid)
@ -354,8 +362,7 @@ namespace Artemis.Core.Services
} }
// Finally fall back to a default layout // Finally fall back to a default layout
if (!device.DisableDefaultLayout) layout = ArtemisLayout.GetDefaultLayout(device);
layout = ArtemisLayout.GetDefaultLayout(device);
ApplyDeviceLayout(device, layout); ApplyDeviceLayout(device, layout);
return layout; return layout;
} }
@ -365,7 +372,7 @@ namespace Artemis.Core.Services
if (layout == null) if (layout == null)
{ {
if (device.Layout != null) if (device.Layout != null)
device.ClearLayout(); ReloadDevice(device);
return; return;
} }
@ -377,6 +384,16 @@ namespace Artemis.Core.Services
UpdateLedGroup(); UpdateLedGroup();
} }
private void ReloadDevice(ArtemisDevice device)
{
DeviceProvider deviceProvider = device.DeviceProvider;
// Feels bad but need to in order to get the initial LEDs back
_pluginManagementService.DisablePluginFeature(deviceProvider, false);
Thread.Sleep(500);
_pluginManagementService.EnablePluginFeature(deviceProvider, false);
}
public ArtemisDevice? GetDevice(IRGBDevice rgbDevice) public ArtemisDevice? GetDevice(IRGBDevice rgbDevice)
{ {
return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice); return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice);

View File

@ -357,6 +357,8 @@ namespace Artemis.Core.Services
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
if (profileEntity != null) if (profileEntity != null)
_profileRepository.Remove(profileEntity); _profileRepository.Remove(profileEntity);
profileConfiguration.Dispose();
} }
public void SaveProfileCategory(ProfileCategory profileCategory) public void SaveProfileCategory(ProfileCategory profileCategory)

View File

@ -1,7 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using Newtonsoft.Json; using Newtonsoft.Json;

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using EmbedIO.WebApi; using EmbedIO.WebApi;

View File

@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using EmbedIO.WebApi; using EmbedIO.WebApi;

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
namespace Artemis.Core namespace Artemis.Core
{ {

View File

@ -1,5 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Stylet; using Stylet;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared

View File

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Stylet; using Stylet;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared

View File

@ -272,6 +272,7 @@ namespace Artemis.UI.Shared.Input
_updateTimer.Dispose(); _updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed; _updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
DataModelViewModel?.Dispose();
DataModelPath?.Dispose(); DataModelPath?.Dispose();
} }
} }

View File

@ -4,7 +4,7 @@ using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignColors.ColorManipulation; using MaterialDesignColors.ColorManipulation;
using Stylet; using Stylet;

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared

View File

@ -2,7 +2,7 @@
using System.Collections; using System.Collections;
using System.Windows.Documents; using System.Windows.Documents;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;

View File

@ -1,6 +1,6 @@
using System; using System;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared

View File

@ -1,6 +1,6 @@
using System; using System;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared

View File

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
@ -13,7 +14,7 @@ namespace Artemis.UI.Shared
/// <summary> /// <summary>
/// Represents a base class for a view model that visualizes a part of the data model /// Represents a base class for a view model that visualizes a part of the data model
/// </summary> /// </summary>
public abstract class DataModelVisualizationViewModel : PropertyChangedBase public abstract class DataModelVisualizationViewModel : PropertyChangedBase, IDisposable
{ {
private const int MaxDepth = 4; private const int MaxDepth = 4;
private BindableCollection<DataModelVisualizationViewModel> _children; private BindableCollection<DataModelVisualizationViewModel> _children;
@ -22,6 +23,7 @@ namespace Artemis.UI.Shared
private bool _isVisualizationExpanded; private bool _isVisualizationExpanded;
private DataModelVisualizationViewModel? _parent; private DataModelVisualizationViewModel? _parent;
private DataModelPropertyAttribute? _propertyDescription; private DataModelPropertyAttribute? _propertyDescription;
private bool _populatedStaticChildren;
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath) internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
{ {
@ -206,21 +208,26 @@ namespace Artemis.UI.Shared
if (modelType == null) if (modelType == null)
throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type"); throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type");
// Add missing static children // Add missing static children only once, they're static after all
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken)) if (!_populatedStaticChildren)
{ {
string childPath = AppendToPath(propertyInfo.Name); foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) {
continue; string childPath = AppendToPath(propertyInfo.Name);
if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null) if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue; continue;
MethodInfo? getMethod = propertyInfo.GetGetMethod(); if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null)
if (getMethod == null || getMethod.GetParameters().Any()) continue;
continue; MethodInfo? getMethod = propertyInfo.GetGetMethod();
if (getMethod == null || getMethod.GetParameters().Any())
continue;
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth()); DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null) if (child != null)
Children.Add(child); Children.Add(child);
}
_populatedStaticChildren = true;
} }
// Remove static children that should be hidden // Remove static children that should be hidden
@ -302,7 +309,14 @@ namespace Artemis.UI.Shared
private string AppendToPath(string toAppend) private string AppendToPath(string toAppend)
{ {
return !string.IsNullOrEmpty(Path) ? $"{Path}.{toAppend}" : toAppend; if (string.IsNullOrEmpty(Path))
return toAppend;
StringBuilder builder = new();
builder.Append(Path);
builder.Append(".");
builder.Append(toAppend);
return builder.ToString();
} }
private void RequestUpdate() private void RequestUpdate()
@ -327,5 +341,33 @@ namespace Artemis.UI.Shared
} }
#endregion #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)
{
DataModelPath?.Dispose();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Dispose(true);
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Input; using Artemis.UI.Shared.Input;

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display; using Artemis.UI.Shared.DefaultTypes.DataModel.Display;

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Input; using Artemis.UI.Shared.Input;

View File

@ -7,10 +7,8 @@ using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Security.Principal; using System.Security.Principal;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Screens;
using Artemis.UI.Utilities; using Artemis.UI.Utilities;
using Ninject; using Ninject;
using Ookii.Dialogs.Wpf; using Ookii.Dialogs.Wpf;
@ -44,30 +42,82 @@ namespace Artemis.UI
if (createdNew) if (createdNew)
return false; return false;
try return RemoteFocus();
{
// Blocking is required here otherwise Artemis shuts down before the remote call gets a chance to finish
RemoteFocus().GetAwaiter().GetResult();
}
catch (Exception)
{
// Not much could go wrong here but this code runs so early it'll crash if something does go wrong
return true;
}
return true;
} }
private async Task RemoteFocus() public void DisplayException(Exception e)
{
using TaskDialog dialog = new();
AssemblyInformationalVersionAttribute versionAttribute = typeof(ApplicationStateManager).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
dialog.WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
dialog.MainInstruction = "Unfortunately Artemis ran into an unhandled exception and cannot continue.";
dialog.Content = e.Message;
dialog.ExpandedInformation = e.StackTrace.Trim();
dialog.CollapsedControlText = "Show stack trace";
dialog.ExpandedControlText = "Hide stack trace";
dialog.Footer = "If this keeps happening check out the <a href=\"https://wiki.artemis-rgb.com\">wiki</a> or hit us up on <a href=\"https://discord.gg/S3MVaC9\">Discord</a>.";
dialog.FooterIcon = TaskDialogIcon.Error;
dialog.EnableHyperlinks = true;
dialog.HyperlinkClicked += OpenHyperlink;
TaskDialogButton copyButton = new("Copy stack trace");
TaskDialogButton closeButton = new("Close") {Default = true};
dialog.Buttons.Add(copyButton);
dialog.Buttons.Add(closeButton);
dialog.ButtonClicked += (_, args) =>
{
if (args.Item == copyButton)
{
Clipboard.SetText(e.ToString());
args.Cancel = true;
}
};
dialog.ShowDialog(Application.Current.MainWindow);
}
private bool RemoteFocus()
{ {
// At this point we cannot read the database yet to retrieve the web server port. // At this point we cannot read the database yet to retrieve the web server port.
// Instead use the method external applications should use as well. // Instead use the method external applications should use as well.
if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt"))) if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt")))
return; {
KillOtherInstances();
return false;
}
string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt")); string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new(); using HttpClient client = new();
await client.PostAsync(url + "remote/bring-to-foreground", null!); try
{
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"));
httpResponseMessage.EnsureSuccessStatusCode();
return true;
}
catch (Exception)
{
KillOtherInstances();
return false;
}
}
private void KillOtherInstances()
{
// Kill everything else heh
List<Process> processes = Process.GetProcessesByName("Artemis.UI").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
foreach (Process process in processes)
{
try
{
process.Kill(true);
}
catch (Exception)
{
// ignored
}
}
} }
private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e) private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e)
@ -134,39 +184,6 @@ namespace Artemis.UI
Execute.OnUIThread(() => Application.Current.Shutdown()); Execute.OnUIThread(() => Application.Current.Shutdown());
} }
public void DisplayException(Exception e)
{
using TaskDialog dialog = new();
AssemblyInformationalVersionAttribute versionAttribute = typeof(ApplicationStateManager).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
dialog.WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
dialog.MainInstruction = "Unfortunately Artemis ran into an unhandled exception and cannot continue.";
dialog.Content = e.Message;
dialog.ExpandedInformation = e.StackTrace.Trim();
dialog.CollapsedControlText = "Show stack trace";
dialog.ExpandedControlText = "Hide stack trace";
dialog.Footer = "If this keeps happening check out the <a href=\"https://wiki.artemis-rgb.com\">wiki</a> or hit us up on <a href=\"https://discord.gg/S3MVaC9\">Discord</a>.";
dialog.FooterIcon = TaskDialogIcon.Error;
dialog.EnableHyperlinks = true;
dialog.HyperlinkClicked += OpenHyperlink;
TaskDialogButton copyButton = new("Copy stack trace");
TaskDialogButton closeButton = new("Close") {Default = true};
dialog.Buttons.Add(copyButton);
dialog.Buttons.Add(closeButton);
dialog.ButtonClicked += (_, args) =>
{
if (args.Item == copyButton)
{
Clipboard.SetText(e.ToString());
args.Cancel = true;
}
};
dialog.ShowDialog(Application.Current.MainWindow);
}
private void OpenHyperlink(object sender, HyperlinkClickedEventArgs e) private void OpenHyperlink(object sender, HyperlinkClickedEventArgs e)
{ {
ProcessStartInfo processInfo = new() ProcessStartInfo processInfo = new()

View File

@ -1,4 +1,4 @@
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared; using Artemis.UI.Shared;
namespace Artemis.UI.DefaultTypes.DataModel.Input namespace Artemis.UI.DefaultTypes.DataModel.Input

View File

@ -1,7 +1,7 @@
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared; using Artemis.UI.Shared;
namespace Artemis.UI.DefaultTypes.DataModel.Input namespace Artemis.UI.DefaultTypes.DataModel.Input

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Stylet; using Stylet;

View File

@ -1,6 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared; using Artemis.UI.Shared;
namespace Artemis.UI.DefaultTypes.DataModel.Input namespace Artemis.UI.DefaultTypes.DataModel.Input

View File

@ -1,4 +1,4 @@
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using SkiaSharp; using SkiaSharp;

View File

@ -1,4 +1,4 @@
using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules;
using Artemis.UI.Shared; using Artemis.UI.Shared;
namespace Artemis.UI.DefaultTypes.DataModel.Input namespace Artemis.UI.DefaultTypes.DataModel.Input

View File

@ -9,6 +9,7 @@ using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects;
@ -220,7 +221,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e)
{ {
PopulateProperties(e.RenderProfileElement); Execute.PostToUIThread(() => PopulateProperties(e.RenderProfileElement));
} }
private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e) private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e)
@ -347,7 +348,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
.ToList(); .ToList();
// Order the effects // Order the effects
List<LayerPropertyGroupViewModel> effectProperties = Items List<LayerPropertyGroupViewModel> effectProperties = Items
.Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot) .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null)
.OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order) .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order)
.ToList(); .ToList();
@ -355,14 +356,16 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
for (int index = 0; index < nonEffectProperties.Count; index++) for (int index = 0; index < nonEffectProperties.Count; index++)
{ {
LayerPropertyGroupViewModel layerPropertyGroupViewModel = nonEffectProperties[index]; LayerPropertyGroupViewModel layerPropertyGroupViewModel = nonEffectProperties[index];
((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index); if (Items.IndexOf(layerPropertyGroupViewModel) != index)
((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index);
} }
// Put the effect properties after, sorted by their order // Put the effect properties after, sorted by their order
for (int index = 0; index < effectProperties.Count; index++) for (int index = 0; index < effectProperties.Count; index++)
{ {
LayerPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index]; LayerPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index];
((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); if (Items.IndexOf(layerPropertyGroupViewModel) != index + nonEffectProperties.Count)
((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count);
} }
} }
@ -527,7 +530,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
Repeating = false; Repeating = false;
} }
} }
private TimeSpan GetCurrentSegmentStart() private TimeSpan GetCurrentSegmentStart()
{ {
TimeSpan current = ProfileEditorService.CurrentTime; TimeSpan current = ProfileEditorService.CurrentTime;
@ -637,7 +640,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
public void TimelineJump(object sender, MouseButtonEventArgs e) public void TimelineJump(object sender, MouseButtonEventArgs e)
{ {
// Get the parent grid, need that for our position // Get the parent grid, need that for our position
IInputElement parent = (IInputElement)VisualTreeHelper.GetParent((DependencyObject)sender); IInputElement parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
double x = Math.Max(0, e.GetPosition(parent).X); double x = Math.Max(0, e.GetPosition(parent).X);
TimeSpan newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond); TimeSpan newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);

View File

@ -15,6 +15,15 @@
d:DataContext="{d:DesignInstance {x:Type profileTree1:ProfileTreeViewModel}}"> d:DataContext="{d:DesignInstance {x:Type profileTree1:ProfileTreeViewModel}}">
<materialDesign:DialogHost IsTabStop="False" Focusable="False" Identifier="ProfileTreeDialog" DialogTheme="Inherit"> <materialDesign:DialogHost IsTabStop="False" Focusable="False" Identifier="ProfileTreeDialog" DialogTheme="Inherit">
<Grid> <Grid>
<Grid.Resources>
<DataTemplate x:Key="ElementDragTemplate" DataType="{x:Type treeItem:TreeItemViewModel}">
<Border Background="{DynamicResource MaterialDesignTextFieldBoxHoverBackground}"
Padding="10"
CornerRadius="4">
<TextBlock Text="{Binding ProfileElement.Name}" VerticalAlignment="Center" Foreground="{DynamicResource MaterialDesignBody}" />
</Border>
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -33,7 +42,10 @@
dd:DragDrop.IsDragSource="True" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}" dd:DragDrop.DropHandler="{Binding}"
ContextMenuOpening="{s:Action ContextMenuOpening}"> dd:DragDrop.DragAdornerTemplate="{StaticResource ElementDragTemplate}"
ContextMenuOpening="{s:Action ContextMenuOpening}"
PreviewMouseDown="{s:Action TreeViewPreviewMouseDown}"
PreviewMouseUp="{s:Action TreeViewPreviewMouseUp}">
<TreeView.InputBindings> <TreeView.InputBindings>
<KeyBinding Key="F2" Command="{s:Action RenameElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" /> <KeyBinding Key="F2" Command="{s:Action RenameElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
<KeyBinding Key="Delete" Command="{s:Action DeleteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" /> <KeyBinding Key="Delete" Command="{s:Action DeleteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
@ -15,6 +16,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IProfileTreeVmFactory _profileTreeVmFactory; private readonly IProfileTreeVmFactory _profileTreeVmFactory;
private bool _draggingTreeView;
private TreeItemViewModel _selectedTreeItem; private TreeItemViewModel _selectedTreeItem;
private bool _updatingTree; private bool _updatingTree;
@ -31,18 +33,24 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
get => _selectedTreeItem; get => _selectedTreeItem;
set set
{ {
if (_updatingTree) return; if (!_updatingTree && SetAndNotify(ref _selectedTreeItem, value) && !_draggingTreeView)
if (!SetAndNotify(ref _selectedTreeItem, value)) return; ApplySelectedTreeItem();
if (value != null && value.ProfileElement is RenderProfileElement renderElement)
_profileEditorService.ChangeSelectedProfileElement(renderElement);
else
_profileEditorService.ChangeSelectedProfileElement(null);
} }
} }
public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement(); public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement();
public void TreeViewPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
_draggingTreeView = true;
}
public void TreeViewPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
_draggingTreeView = false;
ApplySelectedTreeItem();
}
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
Subscribe(); Subscribe();
@ -55,6 +63,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
base.OnClose(); base.OnClose();
} }
private void ApplySelectedTreeItem()
{
if (_selectedTreeItem != null && _selectedTreeItem.ProfileElement is RenderProfileElement renderElement)
_profileEditorService.ChangeSelectedProfileElement(renderElement);
else
_profileEditorService.ChangeSelectedProfileElement(null);
}
private void CreateRootFolderViewModel() private void CreateRootFolderViewModel()
{ {
_updatingTree = true; _updatingTree = true;
@ -64,7 +80,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
ActivateItem(null); ActivateItem(null);
return; return;
} }
ActiveItem = _profileTreeVmFactory.FolderViewModel(folder); ActiveItem = _profileTreeVmFactory.FolderViewModel(folder);
_updatingTree = false; _updatingTree = false;
} }

View File

@ -184,6 +184,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
else else
newSelection = ProfileElement.Parent; newSelection = ProfileElement.Parent;
} }
_profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement);
// Farewell, cruel world // Farewell, cruel world
TreeItemViewModel parent = (TreeItemViewModel) Parent; TreeItemViewModel parent = (TreeItemViewModel) Parent;
@ -191,7 +192,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
parent.RemoveExistingElement(this); parent.RemoveExistingElement(this);
_profileEditorService.SaveSelectedProfileConfiguration(); _profileEditorService.SaveSelectedProfileConfiguration();
_profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement);
} }
public void DuplicateElement() public void DuplicateElement()

View File

@ -87,7 +87,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
protected override void OnClose() protected override void OnClose()
{ {
_updateTimer.Dispose(); _updateTimer.Dispose();
base.OnClose(); base.OnClose();
} }
#endregion #endregion
@ -109,6 +109,9 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
{ {
_updateTimer.Stop(); _updateTimer.Stop();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed; _updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
MainDataModel?.Dispose();
MainDataModel = null;
_pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled; _pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled;

View File

@ -25,9 +25,11 @@ namespace Artemis.UI.Screens.Settings.Device
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IDeviceDebugVmFactory _factory;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private SnackbarMessageQueue _deviceMessageQueue; private SnackbarMessageQueue _deviceMessageQueue;
private BindableCollection<ArtemisLed> _selectedLeds; private BindableCollection<ArtemisLed> _selectedLeds;
private ArtemisDevice _device;
public DeviceDialogViewModel(ArtemisDevice device, public DeviceDialogViewModel(ArtemisDevice device,
ICoreService coreService, ICoreService coreService,
@ -40,22 +42,46 @@ namespace Artemis.UI.Screens.Settings.Device
_deviceService = deviceService; _deviceService = deviceService;
_rgbService = rgbService; _rgbService = rgbService;
_dialogService = dialogService; _dialogService = dialogService;
_factory = factory;
Device = device;
PanZoomViewModel = new PanZoomViewModel(); PanZoomViewModel = new PanZoomViewModel();
SelectedLeds = new BindableCollection<ArtemisLed>(); SelectedLeds = new BindableCollection<ArtemisLed>();
Items.Add(factory.DevicePropertiesTabViewModel(device)); Initialize(device);
if (device.DeviceType == RGBDeviceType.Keyboard) }
Items.Add(factory.InputMappingsTabViewModel(device));
Items.Add(factory.DeviceInfoTabViewModel(device)); private void Initialize(ArtemisDevice device)
Items.Add(factory.DeviceLedsTabViewModel(device)); {
if (SelectedLeds.Any())
ActiveItem = Items.First(); SelectedLeds.Clear();
DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis";
if (Device != null)
Device.DeviceUpdated -= DeviceOnDeviceUpdated;
Device = device;
Device.DeviceUpdated += DeviceOnDeviceUpdated;
int activeTabIndex = 0;
if (Items.Any())
{
activeTabIndex = Items.IndexOf(ActiveItem);
Items.Clear();
}
Items.Add(_factory.DevicePropertiesTabViewModel(Device));
if (Device.DeviceType == RGBDeviceType.Keyboard)
Items.Add(_factory.InputMappingsTabViewModel(Device));
Items.Add(_factory.DeviceInfoTabViewModel(Device));
Items.Add(_factory.DeviceLedsTabViewModel(Device));
ActiveItem = Items[activeTabIndex];
DisplayName = $"{Device.RgbDevice.DeviceInfo.Model} | Artemis";
}
public ArtemisDevice Device
{
get => _device;
set => SetAndNotify(ref _device, value);
} }
public ArtemisDevice Device { get; }
public PanZoomViewModel PanZoomViewModel { get; } public PanZoomViewModel PanZoomViewModel { get; }
public SnackbarMessageQueue DeviceMessageQueue public SnackbarMessageQueue DeviceMessageQueue
@ -84,14 +110,21 @@ namespace Artemis.UI.Screens.Settings.Device
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
_coreService.FrameRendering += CoreServiceOnFrameRendering; _coreService.FrameRendering += CoreServiceOnFrameRendering;
_rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5)); DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
Device.DeviceUpdated += DeviceOnDeviceUpdated;
base.OnInitialActivate(); base.OnInitialActivate();
} }
private void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e)
{
if (e.Device != Device && e.Device.Identifier == Device.Identifier)
Execute.OnUIThread(() => Initialize(e.Device));
}
protected override void OnClose() protected override void OnClose()
{ {
_coreService.FrameRendering -= CoreServiceOnFrameRendering; _coreService.FrameRendering -= CoreServiceOnFrameRendering;
_rgbService.DeviceAdded -= RgbServiceOnDeviceAdded;
Device.DeviceUpdated -= DeviceOnDeviceUpdated; Device.DeviceUpdated -= DeviceOnDeviceUpdated;
base.OnClose(); base.OnClose();
} }

View File

@ -202,12 +202,12 @@
The device layout is used to determine the position of LEDs and to create the visual representation of the device you see on the left side of this window. The device layout is used to determine the position of LEDs and to create the visual representation of the device you see on the left side of this window.
</TextBlock> </TextBlock>
<CheckBox Margin="0 0 0 5" Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding UseDefaultLayout}"> <CheckBox Margin="0 0 0 5" Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding Device.DisableDefaultLayout, Delay=300}">
<CheckBox.Content> <CheckBox.Content>
<TextBlock Margin="0 -5 0 0"> <TextBlock Margin="0 -5 0 0">
Use default layout if needed Don't load default layout
<materialDesign:PackIcon Kind="HelpCircle" <materialDesign:PackIcon Kind="HelpCircle"
ToolTip="If there is no built-in layout and no custom layout, with this enabled Artemis will fall back to the default layout of this device type." /> ToolTip="With this enabled Artemis will not load a layout for this device unless you specifically provide one." />
</TextBlock> </TextBlock>
</CheckBox.Content> </CheckBox.Content>
</CheckBox> </CheckBox>

View File

@ -101,7 +101,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
get => _displayOnDevices; get => _displayOnDevices;
set => SetAndNotify(ref _displayOnDevices, value); set => SetAndNotify(ref _displayOnDevices, value);
} }
// This solution won't scale well but I don't expect there to be many more categories. // This solution won't scale well but I don't expect there to be many more categories.
// If for some reason there will be, dynamically creating a view model per category may be more appropriate // If for some reason there will be, dynamically creating a view model per category may be more appropriate
public bool HasDeskCategory public bool HasDeskCategory
@ -134,16 +134,6 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
set => SetCategory(DeviceCategory.Peripherals, value); set => SetCategory(DeviceCategory.Peripherals, value);
} }
public bool UseDefaultLayout
{
get => !Device.DisableDefaultLayout;
set
{
Device.DisableDefaultLayout = !value;
NotifyOfPropertyChange(nameof(UseDefaultLayout));
}
}
public void ApplyScaling() public void ApplyScaling()
{ {
Device.RedScale = RedScale / 100f; Device.RedScale = RedScale / 100f;
@ -266,7 +256,9 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e) private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout)) if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout))
_rgbService.ApplyBestDeviceLayout(Device); {
Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
}
} }
private void OnFrameRendering(object sender, FrameRenderingEventArgs e) private void OnFrameRendering(object sender, FrameRenderingEventArgs e)

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -10,9 +11,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
{ {
public class DeviceSettingsTabViewModel : Conductor<DeviceSettingsViewModel>.Collection.AllActive public class DeviceSettingsTabViewModel : Conductor<DeviceSettingsViewModel>.Collection.AllActive
{ {
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IRgbService _rgbService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService;
private readonly ISettingsVmFactory _settingsVmFactory;
private bool _confirmedDisable; private bool _confirmedDisable;
public DeviceSettingsTabViewModel(IRgbService rgbService, IDialogService dialogService, ISettingsVmFactory settingsVmFactory) public DeviceSettingsTabViewModel(IRgbService rgbService, IDialogService dialogService, ISettingsVmFactory settingsVmFactory)
@ -24,28 +25,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
_settingsVmFactory = settingsVmFactory; _settingsVmFactory = settingsVmFactory;
} }
#region Overrides of AllActive
/// <inheritdoc />
protected override void OnActivate()
{
// Take it off the UI thread to avoid freezing on tab change
Task.Run(async () =>
{
if (Items.Any())
Items.Clear();
await Task.Delay(200);
List<DeviceSettingsViewModel> instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList();
foreach (DeviceSettingsViewModel deviceSettingsViewModel in instances)
Items.Add(deviceSettingsViewModel);
});
base.OnActivate();
}
#endregion
public async Task<bool> ShowDeviceDisableDialog() public async Task<bool> ShowDeviceDisableDialog()
{ {
if (_confirmedDisable) if (_confirmedDisable)
@ -61,5 +40,49 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
return confirmed; return confirmed;
} }
private void RgbServiceOnDeviceRemoved(object sender, DeviceEventArgs e)
{
DeviceSettingsViewModel viewModel = Items.FirstOrDefault(i => i.Device == e.Device);
if (viewModel != null)
Items.Remove(viewModel);
}
private void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e)
{
Items.Add(_settingsVmFactory.CreateDeviceSettingsViewModel(e.Device));
}
#region Overrides of AllActive
/// <inheritdoc />
protected override void OnActivate()
{
_rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
_rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved;
// Take it off the UI thread to avoid freezing on tab change
Task.Run(async () =>
{
if (Items.Any())
Items.Clear();
await Task.Delay(200);
List<DeviceSettingsViewModel> instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList();
foreach (DeviceSettingsViewModel deviceSettingsViewModel in instances)
Items.Add(deviceSettingsViewModel);
});
base.OnActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
_rgbService.DeviceAdded -= RgbServiceOnDeviceAdded;
_rgbService.DeviceRemoved -= RgbServiceOnDeviceRemoved;
base.OnClose();
}
#endregion
} }
} }

View File

@ -218,27 +218,6 @@
</Grid> </Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" /> <Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Automatically install updates</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
If enabled updates are installed automatically without asking first.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="{Binding AutoInstallUpdates}" IsEnabled="{Binding CheckForUpdates}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />

View File

@ -158,20 +158,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
_settingsService.GetSetting("UI.CheckForUpdates", true).Value = value; _settingsService.GetSetting("UI.CheckForUpdates", true).Value = value;
_settingsService.GetSetting("UI.CheckForUpdates", true).Save(); _settingsService.GetSetting("UI.CheckForUpdates", true).Save();
NotifyOfPropertyChange(nameof(CheckForUpdates)); NotifyOfPropertyChange(nameof(CheckForUpdates));
if (!value)
AutoInstallUpdates = false;
}
}
public bool AutoInstallUpdates
{
get => _settingsService.GetSetting("UI.AutoInstallUpdates", false).Value;
set
{
_settingsService.GetSetting("UI.AutoInstallUpdates", false).Value = value;
_settingsService.GetSetting("UI.AutoInstallUpdates", false).Save();
NotifyOfPropertyChange(nameof(AutoInstallUpdates));
} }
} }

View File

@ -91,14 +91,10 @@
<StackPanel> <StackPanel>
<StackPanel.Resources> <StackPanel.Resources>
<DataTemplate x:Key="CategoryDragTemplate" DataType="{x:Type sidebar:SidebarCategoryViewModel}"> <DataTemplate x:Key="CategoryDragTemplate" DataType="{x:Type sidebar:SidebarCategoryViewModel}">
<DataTemplate.Resources> <Border Background="{DynamicResource MaterialDesignTextFieldBoxHoverBackground}"
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" /> Padding="10"
</DataTemplate.Resources> CornerRadius="4">
<Border Background="{DynamicResource MaterialDesignTextFieldBoxHoverBackground}" <TextBlock Text="{Binding ProfileCategory.Name}" VerticalAlignment="Center" Foreground="{DynamicResource MaterialDesignBody}" />
Padding="10"
CornerRadius="4"
Visibility="{Binding Path=ProfileCategory, FallbackValue=null, Converter={StaticResource NullToVisibilityConverter}}">
<TextBlock Text="{Binding ProfileCategory.Name,TargetNullValue='Oops, I am missing'}" VerticalAlignment="Center" Foreground="{DynamicResource MaterialDesignBody}" />
</Border> </Border>
</DataTemplate> </DataTemplate>
</StackPanel.Resources> </StackPanel.Resources>

View File

@ -69,7 +69,7 @@ namespace Artemis.UI.Screens.Sidebar
private void UpdateHeaderDevice() private void UpdateHeaderDevice()
{ {
HeaderDevice = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard && d.Layout.IsValid); HeaderDevice = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard && d.Layout is {IsValid: true});
} }
public ArtemisDevice HeaderDevice public ArtemisDevice HeaderDevice

View File

@ -28,7 +28,6 @@ namespace Artemis.UI.Services
private const double UpdateCheckInterval = 3600000; // once per hour private const double UpdateCheckInterval = 3600000; // once per hour
private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/"; private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/";
private readonly PluginSetting<bool> _autoInstallUpdates;
private readonly PluginSetting<bool> _checkForUpdates; private readonly PluginSetting<bool> _checkForUpdates;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -42,7 +41,6 @@ namespace Artemis.UI.Services
_windowService.MainWindowOpened += WindowServiceOnMainWindowOpened; _windowService.MainWindowOpened += WindowServiceOnMainWindowOpened;
_checkForUpdates = settingsService.GetSetting("UI.CheckForUpdates", true); _checkForUpdates = settingsService.GetSetting("UI.CheckForUpdates", true);
_autoInstallUpdates = settingsService.GetSetting("UI.AutoInstallUpdates", false);
_checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged; _checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged;
Timer timer = new(UpdateCheckInterval); Timer timer = new(UpdateCheckInterval);
@ -136,18 +134,7 @@ namespace Artemis.UI.Services
if (_windowService.IsMainWindowOpen) if (_windowService.IsMainWindowOpen)
await OfferUpdate(buildInfo); await OfferUpdate(buildInfo);
else if (_autoInstallUpdates.Value)
{
new ToastContentBuilder()
.AddText("Installing new version", AdaptiveTextStyle.Header)
.AddText($"Build {buildNumberDisplay} is available, currently on {Constants.BuildInfo.BuildNumberDisplay}.")
.AddProgressBar(null, null, true)
.Show();
await ApplyUpdate();
}
else else
{
// If auto-install is disabled and the window is closed, best we can do is notify the user and stop. // If auto-install is disabled and the window is closed, best we can do is notify the user and stop.
new ToastContentBuilder() new ToastContentBuilder()
.AddText("New version available", AdaptiveTextStyle.Header) .AddText("New version available", AdaptiveTextStyle.Header)
@ -155,7 +142,6 @@ namespace Artemis.UI.Services
.AddButton("Update", ToastActivationType.Background, "update") .AddButton("Update", ToastActivationType.Background, "update")
.AddButton("Later", ToastActivationType.Background, "later") .AddButton("Later", ToastActivationType.Background, "later")
.Show(t => t.Activated += TOnActivated); .Show(t => t.Activated += TOnActivated);
}
return true; return true;
} }