diff --git a/README.md b/README.md index 42694ef1f..064e062dd 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,27 @@ Artemis 1 is no longer supported and Artemis 2 is in active development. This en **Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!) **Plugin documentation**: https://artemis-rgb.com/docs/ -**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested. If you run into any issues please let us know on Discord.** +**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested. If you run into any issues please let us know on Discord.** +A full list of supported devices can be found on the wiki [here](https://wiki.artemis-rgb.com/en/guides/user/devices). #### Want to build? Follow these instructions 1. Create a central folder like ```C:\Repos``` 2. Clone RGB.NET's [development branch](https://github.com/DarthAffe/RGB.NET/tree/Development) into ```\RGB.NET``` 3. Clone Artemis into ```\Artemis``` +4. Clone Artemis.Plugins [master branch](https://github.com/Artemis-RGB/Artemis.Plugins/tree/master) into ```\Artemis.Plugins``` 5. Open ```\RGB.NET\RGB.NET.sln``` and build with the default config -4. Open ```\Artemis\src\Artemis.sln``` -5. Restore Nuget packages +6. Open ```\Artemis\src\Artemis.sln``` and build as Debug +7. Open ```\Artemis.Plugins\src\Artemis.Plugins.sln``` and build as Debug +8. Restore Nuget packages ##### Alternatively in PowerShell ```powershell git clone https://github.com/DarthAffe/RGB.NET -b Development RGB.NET git clone https://github.com/Artemis-RGB/Artemis Artemis +git clone https://github.com/Artemis-RGB/Artemis.Plugins Artemis.Plugins dotnet build .\RGB.NET\RGB.NET.sln dotnet build .\Artemis\src\Artemis.sln +dotnet build .\Artemis.Plugins\src\Artemis.Plugins.sln ``` For an up-to-date overview of what's currently being worked on, see the [Projects](https://github.com/SpoinkyNL/Artemis/projects) page diff --git a/ci/azure-pipelines-rgbnet.yml b/ci/azure-pipelines-rgbnet.yml index db227c01b..1b8b0db88 100644 --- a/ci/azure-pipelines-rgbnet.yml +++ b/ci/azure-pipelines-rgbnet.yml @@ -37,6 +37,7 @@ steps: inputs: command: 'build' projects: '$(rgbSolution)' + arguments: '--configuration Release' - task: PublishPipelineArtifact@1 displayName: 'Upload build to Azure Pipelines' diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 7d6c52363..5a6e7bef3 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -68,6 +68,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs index b076134bb..75818ac95 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs @@ -36,9 +36,6 @@ namespace Artemis.Core /// public override void ApplyValue(float value) { - if (ValueTypeSetExpression == null) - return; - if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max) value = Math.Min(value, max); if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min) diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index bfeb477f4..99a7b614a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -6,7 +6,7 @@ internal BoolLayerProperty() { KeyframesSupported = false; - RegisterDataBindingProperty(b => b, new GeneralDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 449461bec..387c2d9ef 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,17 +1,43 @@ -namespace Artemis.Core +using System.ComponentModel; +using SkiaSharp; + +namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty { + private ColorGradient? _subscribedGradient; + internal ColorGradientLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; + DataBindingsSupported = true; DefaultValue = new ColorGradient(); - + CurrentValueSet += OnCurrentValueSet; } + private void CreateDataBindingRegistrations() + { + ClearDataBindingProperties(); + if (CurrentValue == null) + return; + + for (int index = 0; index < CurrentValue.Stops.Count; index++) + { + int stopIndex = index; + + void Setter(SKColor value) + { + CurrentValue.Stops[stopIndex].Color = value; + CurrentValue.OnColorValuesUpdated(); + } + + RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}"); + } + } + + /// /// Implicitly converts an to a /// @@ -31,6 +57,22 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); + + if (_subscribedGradient != BaseValue) + { + if (_subscribedGradient != null) + _subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged; + _subscribedGradient = BaseValue; + _subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged; + } + + CreateDataBindingRegistrations(); + } + + private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count) + CreateDataBindingRegistrations(); } #region Overrides of LayerProperty @@ -41,10 +83,31 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); - + base.OnInitialize(); } #endregion } + + internal class ColorStopDataBindingConverter : DataBindingConverter + { + public ColorStopDataBindingConverter() + { + SupportsInterpolate = true; + SupportsSum = true; + } + + /// + public override SKColor Sum(SKColor a, SKColor b) + { + return a.Sum(b); + } + + /// + public override SKColor Interpolate(SKColor a, SKColor b, double progress) + { + return a.Interpolate(b, (float) progress); + } + } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index 55221deaf..cb1a6eae8 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -5,7 +5,7 @@ { internal FloatLayerProperty() { - RegisterDataBindingProperty(value => value, new FloatDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 4b54aca95..298e824f3 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,8 +5,8 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter(), "Start"); + RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index 7edd5241d..f69a1bdbc 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal IntLayerProperty() { - RegisterDataBindingProperty(value => value, new IntDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 2a9f498fe..474fa33e1 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -5,8 +5,8 @@ { internal IntRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new IntDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new IntDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter(), "Start"); + RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index 366c4aa49..46370568a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal SKColorLayerProperty() { - RegisterDataBindingProperty(value => value, new SKColorDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 74befb209..3cd654ad9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter()); - RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter(), "X"); + RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter(), "Y"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index 0d98286b6..3274c26f9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter()); - RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter(), "Width"); + RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter(), "Height"); } /// diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs index 49a2a0072..8ae6d5411 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs @@ -11,9 +11,8 @@ namespace Artemis.Core public class DataModelConditionEvent : DataModelConditionPart { private bool _disposed; - private IDataModelEvent? _event; - private bool _eventTriggered; private bool _reinitializing; + private DateTime _lastTrigger; /// /// Creates a new instance of the class @@ -53,22 +52,21 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataModelConditionEvent"); - // Ensure the event has not been replaced - if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent) - SubscribeToDataModelEvent(dataModelEvent); - - // Only evaluate to true once every time the event has been triggered - if (!_eventTriggered) + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + return false; + // Only evaluate to true once every time the event has been triggered since the last evaluation + if (dataModelEvent.LastTrigger <= _lastTrigger) return false; - _eventTriggered = false; + _lastTrigger = DateTime.Now; // If there is a child (root group), it must evaluate to true whenever the event triggered if (Children.Any()) - return Children[0].EvaluateObject(_event?.LastEventArgumentsUntyped); + return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped); // If there are no children, we always evaluate to true whenever the event triggered return true; + } /// @@ -132,7 +130,7 @@ namespace Artemis.Core // Target list EventPath?.Save(); Entity.EventPath = EventPath?.Entity; - + // Children Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); @@ -172,16 +170,9 @@ namespace Artemis.Core Entity.Children.Clear(); AddChild(new DataModelConditionGroup(this)); } - } - private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent) - { - if (_event != null) - _event.EventTriggered -= OnEventTriggered; - - _event = dataModelEvent; - if (_event != null) - _event.EventTriggered += OnEventTriggered; + if (EventPath?.GetValue() is IDataModelEvent dataModelEvent) + _lastTrigger = dataModelEvent.LastTrigger; } private Type? GetEventArgumentType() @@ -212,11 +203,6 @@ namespace Artemis.Core #region Event handlers - private void OnEventTriggered(object? sender, EventArgs e) - { - _eventTriggered = true; - } - private void EventPathOnPathValidated(object? sender, EventArgs e) { if (_reinitializing) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 18c18894c..4bfbacc48 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -97,7 +97,7 @@ namespace Artemis.Core /// public Type? GetTargetType() { - return Registration?.PropertyExpression.ReturnType; + return Registration?.Getter.Method.ReturnType; } private void ResetEasing(TProperty value) @@ -272,7 +272,7 @@ namespace Artemis.Core throw new ObjectDisposedException("DataBinding"); // General - DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.TargetExpression); + DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.Identifier); if (registration != null) ApplyRegistration(registration); @@ -293,7 +293,7 @@ namespace Artemis.Core // Don't save an invalid state if (Registration != null) - Entity.TargetExpression = Registration.PropertyExpression.ToString(); + Entity.Identifier = Registration.DisplayName; Entity.EasingTime = EasingTime; Entity.EasingFunction = (int) EasingFunction; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index fe4254c22..2b3d7eb5a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -1,6 +1,4 @@ using System; -using System.Linq.Expressions; -using System.Reflection; namespace Artemis.Core { @@ -10,21 +8,6 @@ namespace Artemis.Core /// public abstract class DataBindingConverter : IDataBindingConverter { - /// - /// Gets a dynamically compiled getter pointing to the data bound property - /// - public Func? GetExpression { get; private set; } - - /// - /// Gets a dynamically compiled setter pointing to the data bound property used for value types - /// - public Action? ValueTypeSetExpression { get; private set; } - - /// - /// Gets a dynamically compiled setter pointing to the data bound property used for reference types - /// - public Action? ReferenceTypeSetExpression { get; private set; } - /// /// Gets the data binding this converter is applied to /// @@ -40,9 +23,6 @@ namespace Artemis.Core /// public bool SupportsInterpolate { get; protected set; } - /// - public Type SupportedType => typeof(TProperty); - /// /// Returns the sum of and /// @@ -65,12 +45,9 @@ namespace Artemis.Core /// public virtual void ApplyValue(TProperty value) { - if (DataBinding == null) + if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - if (ReferenceTypeSetExpression != null) - ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value); - else if (ValueTypeSetExpression != null) - ValueTypeSetExpression(value); + DataBinding.Registration.Setter(value); } /// @@ -78,9 +55,9 @@ namespace Artemis.Core /// public virtual TProperty GetValue() { - if (DataBinding == null || GetExpression == null) + if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - return GetExpression(DataBinding.LayerProperty.CurrentValue); + return DataBinding.Registration.Getter(); } /// @@ -104,83 +81,10 @@ namespace Artemis.Core throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration"); DataBinding = dataBinding; - GetExpression = dataBinding.Registration.PropertyExpression.Compile(); - CreateSetExpression(); - OnInitialized(); } - private void CreateSetExpression() - { - // If the registration does not point towards a member of LayerProperty.CurrentValue, assign directly to LayerProperty.CurrentValue - if (DataBinding!.Registration?.Member == null) - { - CreateSetCurrentValueExpression(); - return; - } - - // Ensure the member of LayerProperty.CurrentValue has a setter - MethodInfo? setterMethod = null; - if (DataBinding.Registration.Member is PropertyInfo propertyInfo) - setterMethod = propertyInfo.GetSetMethod(); - // If there is no setter, the built-in data binding cannot do its job, stay null - if (setterMethod == null) - return; - - // If LayerProperty.CurrentValue is a value type, assign it directly to LayerProperty.CurrentValue after applying the changes - if (typeof(TLayerProperty).IsValueType) - CreateSetValueTypeExpression(); - // If it is a reference type it can safely be updated by its reference - else - CreateSetReferenceTypeExpression(); - } - - private void CreateSetReferenceTypeExpression() - { - if (DataBinding!.Registration?.Member == null) - throw new ArtemisCoreException("Cannot create value setter for data binding without a registration"); - - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ParameterExpression parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue"); - MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member); - BinaryExpression assignment = Expression.Assign(memberAccess, propertyValue); - Expression> referenceTypeLambda = Expression.Lambda>(assignment, parameter, propertyValue); - - ReferenceTypeSetExpression = referenceTypeLambda.Compile(); - } - - private void CreateSetValueTypeExpression() - { - if (DataBinding!.Registration?.Member == null) - throw new ArtemisCoreException("Cannot create value setter for data binding without a registration"); - - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ParameterExpression variableCurrent = Expression.Variable(typeof(TLayerProperty), "current"); - ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty); - MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, - DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); - - BlockExpression body = Expression.Block( - new[] {variableCurrent}, - Expression.Assign(variableCurrent, layerPropertyMemberAccess), - Expression.Assign(Expression.MakeMemberAccess(variableCurrent, DataBinding.Registration.Member), propertyValue), - Expression.Assign(layerPropertyMemberAccess, variableCurrent) - ); - - Expression> valueTypeLambda = Expression.Lambda>(body, propertyValue); - ValueTypeSetExpression = valueTypeLambda.Compile(); - } - - private void CreateSetCurrentValueExpression() - { - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ConstantExpression layerProperty = Expression.Constant(DataBinding!.LayerProperty); - MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, - DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); - - BinaryExpression body = Expression.Assign(layerPropertyMemberAccess, propertyValue); - Expression> lambda = Expression.Lambda>(body, propertyValue); - ValueTypeSetExpression = lambda.Compile(); - } + /// + public Type SupportedType => typeof(TProperty); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index 00346e68b..78e233fe1 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -1,7 +1,5 @@ using System; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -9,16 +7,14 @@ namespace Artemis.Core /// public class DataBindingRegistration : IDataBindingRegistration { - internal DataBindingRegistration(LayerProperty layerProperty, - DataBindingConverter converter, - Expression> propertyExpression) + internal DataBindingRegistration(LayerProperty layerProperty, DataBindingConverter converter, + Func getter, Action setter, string displayName) { LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression)); - - if (propertyExpression.Body is MemberExpression memberExpression) - Member = memberExpression.Member; + Getter = getter ?? throw new ArgumentNullException(nameof(getter)); + Setter = setter ?? throw new ArgumentNullException(nameof(setter)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); } /// @@ -32,15 +28,17 @@ namespace Artemis.Core public DataBindingConverter Converter { get; } /// - /// Gets the expression that that accesses the property + /// Gets the function to call to get the value of the property /// - public Expression> PropertyExpression { get; } + public Func Getter { get; } /// - /// Gets the member the targets - /// if the is not a member expression + /// Gets the action to call to set the value of the property /// - public MemberInfo? Member { get; } + public Action Setter { get; } + + /// + public string DisplayName { get; } /// /// Gets the data binding created using this registration @@ -59,7 +57,7 @@ namespace Artemis.Core if (DataBinding != null) return DataBinding; - DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString()); + DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs index fb6220951..8e1de1f0a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs @@ -5,6 +5,11 @@ /// public interface IDataBindingRegistration { + /// + /// Gets or sets the display name of the data binding registration + /// + string DisplayName { get; } + /// /// Returns the data binding applied using this registration /// diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 931ba1d02..9c5953749 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -213,9 +213,25 @@ namespace Artemis.Core Expression? expression = Expression.Convert(parameter, Target.GetType()); Expression? nullCondition = null; + MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!; foreach (DataModelPathSegment segment in _segments) { - BinaryExpression notNull = Expression.NotEqual(expression, Expression.Default(expression.Type)); + BinaryExpression notNull; + try + { + notNull = Expression.NotEqual(expression, Expression.Default(expression.Type)); + } + catch (InvalidOperationException) + { + notNull = Expression.NotEqual( + Expression.Call( + null, + equals, + Expression.Convert(expression, typeof(object)), + Expression.Convert(Expression.Default(expression.Type), typeof(object))), + Expression.Constant(true)); + } + nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull; expression = segment.Initialize(parameter, expression, nullCondition); if (expression == null) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index 33153f4ef..c42b1f74c 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -97,6 +97,16 @@ namespace Artemis.Core /// public event EventHandler? KeyframeRemoved; + /// + /// Occurs when a data binding property has been added + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + /// Occurs when all data binding properties have been removed + /// + public event EventHandler? DataBindingPropertiesCleared; + /// /// Occurs when a data binding has been enabled /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index bb9760a91..61f38c428 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -379,21 +379,16 @@ namespace Artemis.Core public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null); /// - /// Gets a data binding registration by the expression used to register it + /// Gets a data binding registration by the display name used to register it /// Note: The expression must exactly match the one used to register the data binding /// - public DataBindingRegistration? GetDataBindingRegistration(Expression> propertyExpression) - { - return GetDataBindingRegistration(propertyExpression.ToString()); - } - - internal DataBindingRegistration? GetDataBindingRegistration(string expression) + public DataBindingRegistration? GetDataBindingRegistration(string identifier) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.PropertyExpression.ToString() == expression); + registration.DisplayName == identifier); return (DataBindingRegistration?) match; } @@ -410,21 +405,35 @@ namespace Artemis.Core /// Registers a data binding property so that is available to the data binding system /// /// The type of the layer property - /// The expression pointing to the value to register + /// The function to call to get the value of the property + /// The action to call to set the value of the property /// The converter to use while applying the data binding - public void RegisterDataBindingProperty(Expression> propertyExpression, DataBindingConverter converter) + /// The display name of the data binding property + public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, + string displayName) + { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName)) + throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); + + DataBindingRegistration registration = new(this, converter, getter, setter, displayName); + _dataBindingRegistrations.Add(registration); + + OnDataBindingPropertyRegistered(); + return registration; + } + + /// + /// Removes all data binding properties so they are no longer available to the data binding system + /// + public void ClearDataBindingProperties() { if (_disposed) throw new ObjectDisposedException("LayerProperty"); - if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter) - throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'"); - - if (converter.SupportedType != propertyExpression.ReturnType) - throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " + - "because the provided converter does not support the property's type"); - - _dataBindingRegistrations.Add(new DataBindingRegistration(this, converter, propertyExpression)); + _dataBindingRegistrations.Clear(); + OnDataBindingPropertiesCleared(); } /// @@ -661,6 +670,12 @@ namespace Artemis.Core /// public event EventHandler? KeyframeRemoved; + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + public event EventHandler? DataBindingPropertiesCleared; + /// public event EventHandler? DataBindingEnabled; @@ -716,6 +731,22 @@ namespace Artemis.Core KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); } + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertyRegistered() + { + DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertiesCleared() + { + DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this)); + } + /// /// Invokes the event /// diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs index dd3d7ad5b..949065503 100644 --- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs +++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs @@ -51,12 +51,6 @@ namespace Artemis.Core.DeviceProviders /// public bool CanDetectLogicalLayout { get; protected set; } - /// - public override void Disable() - { - // Does not happen with device providers, they require Artemis to restart - } - /// /// Loads a layout for the specified device and wraps it in an /// diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 0575a80ac..53c581e7c 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -178,12 +178,15 @@ namespace Artemis.Core { foreach (PluginFeature feature in Features) feature.Dispose(); + SetEnabled(false); Kernel?.Dispose(); PluginLoader?.Dispose(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + _features.Clear(); - SetEnabled(false); } } diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index b0e08554f..f2fb01fad 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -73,10 +73,10 @@ namespace Artemis.Core if (!enable) { - IsEnabled = false; - // Even if disable failed, still leave it in a disabled state to avoid more issues InternalDisable(); + IsEnabled = false; + OnDisabled(); return; } @@ -129,7 +129,8 @@ namespace Artemis.Core internal virtual void InternalDisable() { - Disable(); + if (IsEnabled) + Disable(); } #region IDisposable @@ -145,7 +146,7 @@ namespace Artemis.Core { if (disposing) { - Disable(); + InternalDisable(); } } diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index 83bab9211..887321a72 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -70,6 +70,12 @@ namespace Artemis.Core.Services /// The resulting plugin Plugin ImportPlugin(string fileName); + /// + /// Unloads and permanently removes the provided plugin + /// + /// The plugin to remove + void RemovePlugin(Plugin plugin); + /// /// Enables the provided plugin feature /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 31dc0070c..66b7933ec 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -5,6 +5,7 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; +using System.Runtime.Loader; using Artemis.Core.DeviceProviders; using Artemis.Core.Ninject; using Artemis.Storage.Entities.Plugins; @@ -275,6 +276,7 @@ namespace Artemis.Core.Services plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure => { configure.IsUnloadable = true; + configure.LoadInMemory = true; configure.PreferSharedTypes = true; }); @@ -430,7 +432,6 @@ namespace Artemis.Core.Services OnPluginDisabled(new PluginEventArgs(plugin)); } - /// public Plugin ImportPlugin(string fileName) { DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins")); @@ -449,7 +450,16 @@ namespace Artemis.Core.Services Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid); if (existing != null) - throw new ArtemisPluginException($"A plugin with the same GUID is already loaded: {existing.Info}"); + { + try + { + RemovePlugin(existing); + } + catch (Exception e) + { + throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e); + } + } string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", ""); string uniqueTargetDirectory = targetDirectory; @@ -464,19 +474,38 @@ namespace Artemis.Core.Services // Extract everything in the same archive directory to the unique plugin directory DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory)); - Directory.CreateDirectory(directoryInfo.FullName); + Utilities.CreateAccessibleDirectory(directoryInfo.FullName); string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, ""); foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries) + { if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory)) { string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length)); - zipArchiveEntry.ExtractToFile(target); + // Create folders + if (zipArchiveEntry.FullName.EndsWith("/")) + Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!); + // Extract files + else + zipArchiveEntry.ExtractToFile(target); } + } // Load the newly extracted plugin and return the result return LoadPlugin(directoryInfo); } + public void RemovePlugin(Plugin plugin) + { + DirectoryInfo directory = plugin.Directory; + lock (_plugins) + { + if (_plugins.Contains(plugin)) + UnloadPlugin(plugin); + } + + directory.Delete(true); + } + #endregion #region Features diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 459f140e7..f29cda2e9 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -71,7 +71,7 @@ namespace Artemis.Core.Services _modifyingProviders = true; List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - Surface.Detach(deviceProvider.Devices); + Surface.Detach(toRemove.Select(d => d.RgbDevice)); foreach (ArtemisDevice device in toRemove) RemoveDevice(device); @@ -115,7 +115,7 @@ namespace Artemis.Core.Services _modifyingProviders = true; List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - Surface.Detach(deviceProvider.Devices); + Surface.Detach(toRemove.Select(d => d.RgbDevice)); foreach (ArtemisDevice device in toRemove) RemoveDevice(device); diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs index 245bd7621..418899d2a 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs @@ -79,6 +79,11 @@ namespace Artemis.Core.Services.Models public void Arrange(List devices) { ArrangedDevices.Clear(); + + // Not much to do here + if (!devices.Any()) + return; + foreach (ArtemisDevice surfaceDevice in devices) { surfaceDevice.X = 0; diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs new file mode 100644 index 000000000..30532aa2b --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; +using EmbedIO; +using Newtonsoft.Json; + +namespace Artemis.Core.Services +{ + /// + /// Represents a plugin web endpoint receiving an object of type and returning any + /// or . + /// Note: Both will be deserialized and serialized respectively using JSON. + /// + public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel + { + private readonly Module? _module; + private readonly DataModelExpansion? _dataModelExpansion; + + internal DataModelJsonPluginEndPoint(Module module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule) + { + _module = module ?? throw new ArgumentNullException(nameof(module)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + internal DataModelJsonPluginEndPoint(DataModelExpansion dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule) + { + _dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + /// + /// Whether or not the end point should throw an exception if deserializing the received JSON fails. + /// If set to malformed JSON is silently ignored; if set to malformed + /// JSON throws a . + /// + public bool ThrowOnFail { get; set; } + + #region Overrides of PluginEndPoint + + /// + protected override async Task ProcessRequest(IHttpContext context) + { + if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put) + throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls"); + + context.Response.ContentType = MimeType.Json; + + using TextReader reader = context.OpenRequestText(); + try + { + if (_module != null) + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel); + else + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel); + } + catch (JsonException) + { + if (ThrowOnFail) + throw; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs new file mode 100644 index 000000000..1e17e1a91 --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Artemis.Core.Services +{ + /// + /// Provides data about endpoint exception related events + /// + public class EndpointExceptionEventArgs : EventArgs + { + internal EndpointExceptionEventArgs(Exception exception) + { + Exception = exception; + } + + /// + /// Gets the exception that occurred + /// + public Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs new file mode 100644 index 000000000..6b483cc06 --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using EmbedIO; + +namespace Artemis.Core.Services +{ + /// + /// Provides data about endpoint request related events + /// + public class EndpointRequestEventArgs : EventArgs + { + internal EndpointRequestEventArgs(IHttpContext context) + { + Context = context; + } + + /// + /// Gets the HTTP context of the request + /// + public IHttpContext Context { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs index c456ff220..c62c41fe3 100644 --- a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs +++ b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs @@ -52,15 +52,65 @@ namespace Artemis.Core.Services /// public string? Returns { get; protected set; } + /// + /// Occurs whenever a request threw an unhandled exception + /// + public event EventHandler? RequestException; + + /// + /// Occurs whenever a request is about to be processed + /// + public event EventHandler? ProcessingRequest; + + /// + /// Occurs whenever a request was processed + /// + public event EventHandler? ProcessedRequest; + /// /// Called whenever the end point has to process a request /// /// The HTTP context of the request protected abstract Task ProcessRequest(IHttpContext context); + /// + /// Invokes the event + /// + /// The exception that occurred during the request + protected virtual void OnRequestException(Exception e) + { + RequestException?.Invoke(this, new EndpointExceptionEventArgs(e)); + } + + /// + /// Invokes the event + /// + protected virtual void OnProcessingRequest(IHttpContext context) + { + ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context)); + } + + /// + /// Invokes the event + /// + protected virtual void OnProcessedRequest(IHttpContext context) + { + ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context)); + } + internal async Task InternalProcessRequest(IHttpContext context) { - await ProcessRequest(context); + try + { + OnProcessingRequest(context); + await ProcessRequest(context); + OnProcessedRequest(context); + } + catch (Exception e) + { + OnRequestException(e); + throw; + } } private void OnDisabled(object? sender, EventArgs e) diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 704a4a19c..ef98d3fa0 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; @@ -43,6 +45,24 @@ namespace Artemis.Core.Services /// The resulting end point JsonPluginEndPoint AddResponsiveJsonEndPoint(PluginFeature feature, string endPointName, Func requestHandler); + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The module whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel; + + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The data model expansion whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel; + /// /// Adds a new endpoint for the given plugin feature receiving an a . /// diff --git a/src/Artemis.Core/Services/WebServer/PluginsModule.cs b/src/Artemis.Core/Services/WebServer/PluginsModule.cs index 78de97e66..d7f9d49de 100644 --- a/src/Artemis.Core/Services/WebServer/PluginsModule.cs +++ b/src/Artemis.Core/Services/WebServer/PluginsModule.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using EmbedIO; -using Newtonsoft.Json; namespace Artemis.Core.Services { @@ -67,6 +64,12 @@ namespace Artemis.Core.Services if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint)) throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}."); + // If Accept-Charset contains a wildcard, remove the header so we default to UTF8 + // This is a workaround for an EmbedIO ehh issue + string? acceptCharset = context.Request.Headers["Accept-Charset"]; + if (acceptCharset != null && acceptCharset.Contains("*")) + context.Request.Headers.Remove("Accept-Charset"); + // It is up to the registration how the request is eventually handled, it might even set a response here await endPoint.InternalProcessRequest(context); diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 41b3911de..8de5ce9e4 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; using Newtonsoft.Json; @@ -28,7 +30,6 @@ namespace Artemis.Core.Services _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; PluginsModule = new PluginsModule("/plugins"); - StartWebServer(); } @@ -42,7 +43,7 @@ namespace Artemis.Core.Services Server?.Dispose(); Server = null; - string url = $"http://localhost:{_webServerPortSetting.Value}/"; + string url = $"http://*:{_webServerPortSetting.Value}/"; WebApiModule apiModule = new("/api/", JsonNetSerializer); PluginsModule.ServerUrl = url; WebServer server = new WebServer(o => o.WithUrlPrefix(url).WithMode(HttpListenerMode.EmbedIO)) @@ -126,6 +127,29 @@ namespace Artemis.Core.Services return endPoint; } + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel + { + if (module == null) throw new ArgumentNullException(nameof(module)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(module, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel + { + if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(dataModelExpansion, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + + private void HandleDataModelRequest(Module module, T value) where T : DataModel + { + + } + public void RemovePluginEndPoint(PluginEndPoint endPoint) { PluginsModule.RemovePluginEndPoint(endPoint); diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs index 0a0dc030c..6dd27b85a 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings { public class DataBindingEntity { - public string TargetExpression { get; set; } + public string Identifier { get; set; } public TimeSpan EasingTime { get; set; } public int EasingFunction { get; set; } diff --git a/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs b/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs new file mode 100644 index 000000000..4b7daf20f --- /dev/null +++ b/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs @@ -0,0 +1,55 @@ +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class M10BetterDataBindings : IStorageMigration + { + private void Migrate(BsonValue bsonValue) + { + if (!bsonValue.IsDocument || !bsonValue.AsDocument.TryGetValue("PropertyEntities", out BsonValue propertyEntities)) + return; + + foreach (BsonValue propertyEntity in propertyEntities.AsArray) + { + if (!propertyEntity.AsDocument.TryGetValue("DataBindingEntities", out BsonValue dataBindingEntities)) + continue; + foreach (BsonValue dataBindingEntity in dataBindingEntities.AsArray) + { + if (!dataBindingEntity.AsDocument.TryGetValue("TargetExpression", out BsonValue targetExpression)) + continue; + string value = targetExpression.AsString; + if (value == "value => value" || value == "b => b") + { + dataBindingEntity.AsDocument["Identifier"] = "Value"; + } + else + { + string selector = value.Split("=>")[1]; + string property = selector.Split(".")[1]; + dataBindingEntity.AsDocument["Identifier"] = property; + } + + dataBindingEntity.AsDocument.Remove("TargetExpression"); + } + } + } + + public int UserVersion => 10; + + public void Apply(LiteRepository repository) + { + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + foreach (BsonDocument bsonDocument in collection.FindAll()) + { + foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray) + Migrate(bsonLayer); + + foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray) + Migrate(bsonLayer); + + collection.Update(bsonDocument); + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index a22295ddd..362b05fa9 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -132,7 +132,7 @@ - + diff --git a/src/Artemis.UI/Converters/UriToFileNameConverter.cs b/src/Artemis.UI/Converters/UriToFileNameConverter.cs new file mode 100644 index 000000000..435c1fec5 --- /dev/null +++ b/src/Artemis.UI/Converters/UriToFileNameConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.IO; +using System.Windows.Data; + +namespace Artemis.UI.Converters +{ + public class UriToFileNameConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Uri uri && uri.IsFile) + return Path.GetFileName(uri.LocalPath); + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml index 16f480241..ebf357f9e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml @@ -1,4 +1,4 @@ - { + private List _registrations; + public BoolPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { + _registrations = layerProperty.GetAllDataBindingRegistrations(); } - public bool IsEnabled => true; + public bool IsEnabled => _registrations.Any(r => r.GetDataBinding() != null); + + protected override void OnDataBindingsChanged() + { + NotifyOfPropertyChange(nameof(IsEnabled)); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml index 15c14f988..aa4e288d3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml @@ -1,12 +1,12 @@ - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs index d0b5e1b16..20409eb1c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -7,7 +7,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.PropertyInput +namespace Artemis.UI.DefaultTypes.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml index 13ea3a9c9..ef17ba34e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml @@ -1,9 +1,8 @@ - { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml index 732d1648f..818914562 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml @@ -1,4 +1,4 @@ - : PropertyInputViewModel where T : Enum { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml index 3c9bd2105..65e27826f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml @@ -1,11 +1,10 @@ - { @@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput public FloatPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml index e88828009..aabe66a33 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); - _endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); + _startRegistration = layerProperty.GetDataBindingRegistration("Start"); + _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public float Start diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml index 14fab93f1..4cae90771 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml @@ -1,11 +1,10 @@ - { @@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput public IntPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml index 62e4b8570..40a0e815a 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); - _endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); + _startRegistration = layerProperty.GetDataBindingRegistration("Start"); + _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public int Start diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml index fdc5a6c1b..6861259bb 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml @@ -1,10 +1,9 @@ - { @@ -11,7 +11,7 @@ namespace Artemis.UI.PropertyInput public SKColorPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml index 34e679a0c..1f15efc15 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml @@ -1,11 +1,10 @@ - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs index 8d7448f8b..ac32cbaf3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs @@ -6,7 +6,7 @@ using FluentValidation; using SkiaSharp; using Stylet; -namespace Artemis.UI.PropertyInput +namespace Artemis.UI.DefaultTypes.PropertyInput { public class SKPointPropertyInputViewModel : PropertyInputViewModel { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput public SKPointPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _xRegistration = layerProperty.GetDataBindingRegistration(point => point.X); - _yRegistration = layerProperty.GetDataBindingRegistration(point => point.Y); + _xRegistration = layerProperty.GetDataBindingRegistration("X"); + _yRegistration = layerProperty.GetDataBindingRegistration("Y"); } public float X diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml index 9cca2e62b..0f5dfc93c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -18,8 +18,8 @@ namespace Artemis.UI.PropertyInput public SKSizePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _widthRegistration = layerProperty.GetDataBindingRegistration(size => size.Width); - _heightRegistration = layerProperty.GetDataBindingRegistration(size => size.Height); + _widthRegistration = layerProperty.GetDataBindingRegistration("Width"); + _heightRegistration = layerProperty.GetDataBindingRegistration("Height"); } // Since SKSize is immutable we need to create properties that replace the SKSize entirely diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 32daa2030..8dd40960e 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -15,6 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Settings.Debug; +using Artemis.UI.Screens.Settings.Device; +using Artemis.UI.Screens.Settings.Device.Tabs; using Artemis.UI.Screens.Settings.Tabs.Devices; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Screens.Shared; @@ -43,7 +45,10 @@ namespace Artemis.UI.Ninject.Factories public interface IDeviceDebugVmFactory : IVmFactory { - DeviceDebugViewModel Create(ArtemisDevice device); + DeviceDialogViewModel DeviceDialogViewModel(ArtemisDevice device); + DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device); + DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device); + DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device); } public interface IProfileTreeVmFactory : IVmFactory diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index cc49f3d59..05c33df00 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -200,7 +200,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}"> RESTART @@ -215,7 +215,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IGNORE @@ -230,7 +230,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> COPY diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index a04d3d563..3f79b19e3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -38,7 +38,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public RenderProfileElement RenderProfileElement { get => _renderProfileElement; - set => SetAndNotify(ref _renderProfileElement, value); + set + { + if (!SetAndNotify(ref _renderProfileElement, value)) return; + NotifyOfPropertyChange(nameof(DisplayContinuously)); + NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); + NotifyOfPropertyChange(nameof(EventOverlapMode)); + } } public bool DisplayContinuously @@ -65,6 +71,17 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions } } + public TimeLineEventOverlapMode EventOverlapMode + { + get => RenderProfileElement?.Timeline.EventOverlapMode ?? TimeLineEventOverlapMode.Restart; + set + { + if (RenderProfileElement == null || RenderProfileElement?.Timeline.EventOverlapMode == value) return; + RenderProfileElement.Timeline.EventOverlapMode = value; + _profileEditorService.UpdateSelectedProfileElement(); + } + } + public bool ConditionBehaviourEnabled => RenderProfileElement != null; protected override void OnInitialActivate() @@ -119,5 +136,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); } + + public void EventTriggerModeSelected() + { + _profileEditorService.UpdateSelectedProfileElement(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 0ba67c4ab..b25710928 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -38,11 +38,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _profileEditorService = profileEditorService; _dataBindingsVmFactory = dataBindingsVmFactory; - if (Registration.Member != null) - DisplayName = Registration.Member.Name.ToUpper(); - else - DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper(); - + DisplayName = Registration.DisplayName.ToUpper(); AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); EasingViewModels = new BindableCollection(); @@ -106,8 +102,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings protected override void OnInitialActivate() { - base.OnInitialActivate(); Initialize(); + base.OnInitialActivate(); } private void Initialize() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml index 35e1c740c..164f28796 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml @@ -6,11 +6,18 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index 23e73dcd1..00c72fc34 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -11,7 +14,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; + private ILayerProperty? _selectedDataBinding; private int _selectedItemIndex; + private bool _updating; public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) { @@ -24,9 +29,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings get => _selectedItemIndex; set => SetAndNotify(ref _selectedItemIndex, value); } - + private void CreateDataBindingViewModels() { + int oldIndex = SelectedItemIndex; Items.Clear(); ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding; @@ -37,26 +43,64 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving // and creating the actual data bindings - foreach (IDataBindingRegistration registration in registrations) - Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration)); + Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration))); - SelectedItemIndex = 0; + SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex; } private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) { CreateDataBindingViewModels(); + SubscribeToSelectedDataBinding(); + + SelectedItemIndex = 0; + } + + private void SubscribeToSelectedDataBinding() + { + if (_selectedDataBinding != null) + { + _selectedDataBinding.DataBindingPropertyRegistered -= DataBindingRegistrationsChanged; + _selectedDataBinding.DataBindingPropertiesCleared -= DataBindingRegistrationsChanged; + } + + _selectedDataBinding = _profileEditorService.SelectedDataBinding; + if (_selectedDataBinding != null) + { + _selectedDataBinding.DataBindingPropertyRegistered += DataBindingRegistrationsChanged; + _selectedDataBinding.DataBindingPropertiesCleared += DataBindingRegistrationsChanged; + } + } + + private void DataBindingRegistrationsChanged(object sender, LayerPropertyEventArgs e) + { + if (_updating) + return; + + _updating = true; + Execute.PostToUIThread(async () => + { + await Task.Delay(200); + CreateDataBindingViewModels(); + _updating = false; + }); } #region Overrides of Screen - /// protected override void OnInitialActivate() { _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; CreateDataBindingViewModels(); + SubscribeToSelectedDataBinding(); base.OnInitialActivate(); } + + protected override void OnActivate() + { + SelectedItemIndex = 0; + base.OnActivate(); + } protected override void OnClose() { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 071884dcd..f3620ba15 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -302,7 +302,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization TimeSpan delta = DateTime.Now - _lastUpdate; _lastUpdate = DateTime.Now; - if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null || _profileEditorService.Playing) + if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null) return; foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() diff --git a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml deleted file mode 100644 index 80dc57da4..000000000 --- a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - In this window you can view detailed information of the device. - Please note that having this window open can have a performance impact on your system. - - - - - - - - - - - - - - - - - - - - - - - - Device name - - - - - - - - - - - - - - - Manufacturer - - - - - - - - - - - - - - - Device type - - - - - - - - - - - - - - - Physical layout - - - - - - - - - - - - - - - Device image - - - - - - - - - - - - - - - Size (1px = 1mm) - - - - - - - - - - - - - - Location (1px = 1mm) - - - - - - - - - - - - - - - Rotation (degrees) - - - - - - - - - - - - - - - Logical layout - - - - - - - - - - - - - - - Layout file path - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index fca0a0970..729c67a9a 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -6,10 +6,10 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs" xmlns:s="https://github.com/canton7/Stylet" xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes" - xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core" xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> @@ -19,6 +19,10 @@ + + + + @@ -29,16 +33,18 @@ - + - Filter module - Filter module + - + + + Slow updates + + + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 442b0996d..0e0e97656 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -20,12 +20,13 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private DataModelPropertiesViewModel _mainDataModel; private string _propertySearch; private Module _selectedModule; + private bool _slowUpdates; public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) { _dataModelUIService = dataModelUIService; _pluginManagementService = pluginManagementService; - _updateTimer = new Timer(500); + _updateTimer = new Timer(25); DisplayName = "Data model"; Modules = new BindableCollection(); @@ -43,6 +44,16 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs set => SetAndNotify(ref _propertySearch, value); } + public bool SlowUpdates + { + get => _slowUpdates; + set + { + SetAndNotify(ref _slowUpdates, value); + _updateTimer.Interval = _slowUpdates ? 500 : 25; + } + } + public BindableCollection Modules { get; } public Module SelectedModule @@ -116,7 +127,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs lock (MainDataModel) { - MainDataModel.Update(_dataModelUIService, null); + MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true)); } } diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml new file mode 100644 index 000000000..7b82c904e --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs similarity index 86% rename from src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs rename to src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index 0eac26446..97d3ce8fb 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -2,36 +2,45 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Xml.Serialization; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Screens.Shared; using Artemis.UI.Shared.Services; using Ookii.Dialogs.Wpf; using RGB.NET.Layout; using Stylet; -// using PropertyChanged; - -namespace Artemis.UI.Screens.Settings.Debug +namespace Artemis.UI.Screens.Settings.Device { - public class DeviceDebugViewModel : Screen + public class DeviceDialogViewModel : Conductor.Collection.OneActive { private readonly IDeviceService _deviceService; private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; private ArtemisLed _selectedLed; - public DeviceDebugViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService) + public DeviceDialogViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) { _deviceService = deviceService; _rgbService = rgbService; _dialogService = dialogService; + Device = device; + PanZoomViewModel = new PanZoomViewModel(); + + Items.Add(factory.DevicePropertiesTabViewModel(device)); + Items.Add(factory.DeviceInfoTabViewModel(device)); + Items.Add(factory.DeviceLedsTabViewModel(device)); + ActiveItem = Items.First(); + DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis"; } - public List SelectedLeds => SelectedLed != null ? new List {SelectedLed} : null; public ArtemisDevice Device { get; } - + public PanZoomViewModel PanZoomViewModel { get; } + public ArtemisLed SelectedLed { get => _selectedLed; @@ -41,6 +50,7 @@ namespace Artemis.UI.Screens.Settings.Debug NotifyOfPropertyChange(nameof(SelectedLeds)); } } + public List SelectedLeds => SelectedLed != null ? new List { SelectedLed } : null; public bool CanOpenImageDirectory => Device.Layout?.Image != null; @@ -135,7 +145,7 @@ namespace Artemis.UI.Screens.Settings.Debug foreach (ArtemisLedLayout ledLayout in Device.Layout.Leds) { - if (ledLayout.LayoutCustomLedData.LogicalLayouts == null) + if (ledLayout.LayoutCustomLedData.LogicalLayouts == null) continue; // Only the image of the current logical layout is available as an URI, iterate each layout and find the images manually diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml new file mode 100644 index 000000000..5b4ce48a4 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + Device name + + + + Manufacturer + + + + + Device type + + + + + Physical layout + + + + + + Size (1px = 1mm) + + + + Location (1px = 1mm) + + + + Rotation (degrees) + + + + + Logical layout + + + + + + + + + + + + + Layout file path + + + + + + + + + + + Image file path + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs new file mode 100644 index 000000000..dcd976f96 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs @@ -0,0 +1,18 @@ +using Artemis.Core; +using RGB.NET.Core; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Device.Tabs +{ + public class DeviceInfoTabViewModel : Screen + { + public DeviceInfoTabViewModel(ArtemisDevice device) + { + Device = device; + DisplayName = "INFO"; + } + + public bool IsKeyboard => Device.RgbDevice is IKeyboard; + public ArtemisDevice Device { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml new file mode 100644 index 000000000..37831d39b --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs new file mode 100644 index 000000000..d57f8cb1a --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs @@ -0,0 +1,17 @@ +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Device.Tabs +{ + public class DeviceLedsTabViewModel : Screen + { + + public DeviceLedsTabViewModel(ArtemisDevice device) + { + Device = device; + DisplayName = "LEDS"; + } + + public ArtemisDevice Device { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml new file mode 100644 index 000000000..56da0c5bd --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + Surface properties + + + + + + + + + + + + + Color calibration + + + + Use the sliders below to adjust the colors of your device so that it matches your other devices. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Custom layout + + + Select a custom layout below if you want to change the appearance and/or LEDs of this device. + + + + + + + Layout path + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs similarity index 73% rename from src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs rename to src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs index ea52a5867..42fffa621 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -5,68 +5,46 @@ using System.Windows.Input; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services; -using MaterialDesignThemes.Wpf; using Ookii.Dialogs.Wpf; using SkiaSharp; using Stylet; -namespace Artemis.UI.Screens.SurfaceEditor.Dialogs +namespace Artemis.UI.Screens.Settings.Device.Tabs { - public class SurfaceDeviceConfigViewModel : DialogViewModelBase + public class DevicePropertiesTabViewModel : Screen { private readonly ICoreService _coreService; - private readonly IRgbService _rgbService; private readonly IMessageService _messageService; - private readonly float _initialRedScale; - private readonly float _initialGreenScale; - private readonly float _initialBlueScale; - private int _rotation; - private float _scale; - private int _x; - private int _y; - private float _redScale; - private float _greenScale; - private float _blueScale; + private readonly IRgbService _rgbService; + private double _blueScale; private SKColor _currentColor; private bool _displayOnDevices; + private double _greenScale; + private double _initialBlueScale; + private double _initialGreenScale; + private double _initialRedScale; + private double _redScale; + private int _rotation; + private double _scale; + private int _x; + private int _y; - public SurfaceDeviceConfigViewModel(ArtemisDevice device, + public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IMessageService messageService, - IModelValidator validator) : base(validator) + IModelValidator validator) : base(validator) { _coreService = coreService; _rgbService = rgbService; _messageService = messageService; Device = device; - - X = (int) Device.X; - Y = (int) Device.Y; - Scale = Device.Scale; - Rotation = (int) Device.Rotation; - RedScale = Device.RedScale * 100f; - GreenScale = Device.GreenScale * 100f; - BlueScale = Device.BlueScale * 100f; - //we need to store the initial values to be able to restore them when the user clicks "Cancel" - _initialRedScale = Device.RedScale; - _initialGreenScale = Device.GreenScale; - _initialBlueScale = Device.BlueScale; - CurrentColor = SKColors.White; - _coreService.FrameRendering += OnFrameRendering; - Device.PropertyChanged += DeviceOnPropertyChanged; + DisplayName = "PROPERTIES"; } public ArtemisDevice Device { get; } - public override void OnDialogClosed(object sender, DialogClosingEventArgs e) - { - _coreService.FrameRendering -= OnFrameRendering; - Device.PropertyChanged -= DeviceOnPropertyChanged; - base.OnDialogClosed(sender, e); - } - public int X { get => _x; @@ -79,7 +57,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs set => SetAndNotify(ref _y, value); } - public float Scale + public double Scale { get => _scale; set => SetAndNotify(ref _scale, value); @@ -91,19 +69,19 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs set => SetAndNotify(ref _rotation, value); } - public float RedScale + public double RedScale { get => _redScale; set => SetAndNotify(ref _redScale, value); } - public float GreenScale + public double GreenScale { get => _greenScale; set => SetAndNotify(ref _greenScale, value); } - public float BlueScale + public double BlueScale { get => _blueScale; set => SetAndNotify(ref _blueScale, value); @@ -121,32 +99,11 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs set => SetAndNotify(ref _displayOnDevices, value); } - public async Task Accept() - { - await ValidateAsync(); - if (HasErrors) - return; - - _coreService.ModuleRenderingDisabled = true; - await Task.Delay(100); - - Device.X = X; - Device.Y = Y; - Device.Scale = Scale; - Device.Rotation = Rotation; - Device.RedScale = RedScale / 100f; - Device.GreenScale = GreenScale / 100f; - Device.BlueScale = BlueScale / 100f; - - _coreService.ModuleRenderingDisabled = false; - Session.Close(true); - } - public void ApplyScaling() { - Device.RedScale = RedScale / 100f; - Device.GreenScale = GreenScale / 100f; - Device.BlueScale = BlueScale / 100f; + Device.RedScale = RedScale / 100d; + Device.GreenScale = GreenScale / 100d; + Device.BlueScale = BlueScale / 100d; } public void BrowseCustomLayout(object sender, MouseEventArgs e) @@ -169,21 +126,58 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs } } - public override void Cancel() + public async Task Apply() + { + await ValidateAsync(); + if (HasErrors) + return; + + _coreService.ModuleRenderingDisabled = true; + await Task.Delay(100); + + Device.X = X; + Device.Y = Y; + Device.Scale = Scale; + Device.Rotation = Rotation; + Device.RedScale = RedScale / 100d; + Device.GreenScale = GreenScale / 100d; + Device.BlueScale = BlueScale / 100d; + + _coreService.ModuleRenderingDisabled = false; + } + + public void Reset() { Device.RedScale = _initialRedScale; Device.GreenScale = _initialGreenScale; Device.BlueScale = _initialBlueScale; - - base.Cancel(); } + protected override void OnActivate() + { + X = (int) Device.X; + Y = (int) Device.Y; + Scale = Device.Scale; + Rotation = (int) Device.Rotation; + RedScale = Device.RedScale * 100d; + GreenScale = Device.GreenScale * 100d; + BlueScale = Device.BlueScale * 100d; + //we need to store the initial values to be able to restore them when the user clicks "Cancel" + _initialRedScale = Device.RedScale; + _initialGreenScale = Device.GreenScale; + _initialBlueScale = Device.BlueScale; + CurrentColor = SKColors.White; + _coreService.FrameRendering += OnFrameRendering; + Device.PropertyChanged += DeviceOnPropertyChanged; + + base.OnActivate(); + } + + #region Event handlers + private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Device.CustomLayoutPath)) - { - _rgbService.ApplyBestDeviceLayout(Device); - } + if (e.PropertyName == nameof(Device.CustomLayoutPath)) _rgbService.ApplyBestDeviceLayout(Device); } private void OnFrameRendering(object sender, FrameRenderingEventArgs e) @@ -197,5 +191,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs }; e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint); } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/SurfaceDeviceConfigViewModelValidator.cs similarity index 73% rename from src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs rename to src/Artemis.UI/Screens/Settings/Device/Tabs/SurfaceDeviceConfigViewModelValidator.cs index 188ace180..54f79cf46 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigViewModelValidator.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/SurfaceDeviceConfigViewModelValidator.cs @@ -1,10 +1,10 @@ using FluentValidation; -namespace Artemis.UI.Screens.SurfaceEditor.Dialogs +namespace Artemis.UI.Screens.Settings.Device.Tabs { - public class SurfaceDeviceConfigViewModelValidator : AbstractValidator + public class DevicePropertiesTabViewModelValidator : AbstractValidator { - public SurfaceDeviceConfigViewModelValidator() + public DevicePropertiesTabViewModelValidator() { RuleFor(m => m.X).GreaterThanOrEqualTo(0).WithMessage("X-coordinate must be 0 or greater"); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml index ced4c9131..a6564ec50 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml @@ -54,12 +54,6 @@ - + SETTINGS + + + + - Plugin enabled + + Plugin enabled + + _pluginManagementService.EnablePlugin(Plugin, true)); diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml deleted file mode 100644 index 561b259f7..000000000 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - - - - - - - - - - - - - - Properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Color calibration - - - - Use the sliders below to adjust the colors of your device so that it matches your other devices. - - - - - - - - Custom layout - - - Select a custom layout below if you want to change the appearance and/or LEDs of this device. - - - - - - - - Layout path - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml index 6cbafa329..0101d01e4 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml @@ -201,6 +201,18 @@ + + + + + Show random device colors + + + + +