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

Merge branch 'master' into RGB.NET_update

This commit is contained in:
Robert 2021-03-05 15:40:15 +01:00
commit da90c0d3a1
94 changed files with 1525 additions and 1007 deletions

View File

@ -16,21 +16,26 @@ Artemis 1 is no longer supported and Artemis 2 is in active development. This en
**Plugin documentation**: https://artemis-rgb.com/docs/ **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 #### Want to build? Follow these instructions
1. Create a central folder like ```C:\Repos``` 1. Create a central folder like ```C:\Repos```
2. Clone RGB.NET's [development branch](https://github.com/DarthAffe/RGB.NET/tree/Development) into ```<central folder>\RGB.NET``` 2. Clone RGB.NET's [development branch](https://github.com/DarthAffe/RGB.NET/tree/Development) into ```<central folder>\RGB.NET```
3. Clone Artemis into ```<central folder>\Artemis``` 3. Clone Artemis into ```<central folder>\Artemis```
4. Clone Artemis.Plugins [master branch](https://github.com/Artemis-RGB/Artemis.Plugins/tree/master) into ```<central folder>\Artemis.Plugins```
5. Open ```<central folder>\RGB.NET\RGB.NET.sln``` and build with the default config 5. Open ```<central folder>\RGB.NET\RGB.NET.sln``` and build with the default config
4. Open ```<central folder>\Artemis\src\Artemis.sln``` 6. Open ```<central folder>\Artemis\src\Artemis.sln``` and build as Debug
5. Restore Nuget packages 7. Open ```<central folder>\Artemis.Plugins\src\Artemis.Plugins.sln``` and build as Debug
8. Restore Nuget packages
##### Alternatively in PowerShell ##### Alternatively in PowerShell
```powershell ```powershell
git clone https://github.com/DarthAffe/RGB.NET -b Development RGB.NET 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 Artemis
git clone https://github.com/Artemis-RGB/Artemis.Plugins Artemis.Plugins
dotnet build .\RGB.NET\RGB.NET.sln dotnet build .\RGB.NET\RGB.NET.sln
dotnet build .\Artemis\src\Artemis.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 For an up-to-date overview of what's currently being worked on, see the [Projects](https://github.com/SpoinkyNL/Artemis/projects) page

View File

@ -37,6 +37,7 @@ steps:
inputs: inputs:
command: 'build' command: 'build'
projects: '$(rgbSolution)' projects: '$(rgbSolution)'
arguments: '--configuration Release'
- task: PublishPipelineArtifact@1 - task: PublishPipelineArtifact@1
displayName: 'Upload build to Azure Pipelines' displayName: 'Upload build to Azure Pipelines'

View File

@ -68,6 +68,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Ccontrollers/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Ccontrollers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cendpoints/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cendpoints/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cendpoints_005Ceventargs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cinterfaces/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cjson/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cjson/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>

View File

@ -36,9 +36,6 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void ApplyValue(float value) public override void ApplyValue(float value)
{ {
if (ValueTypeSetExpression == null)
return;
if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max) if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
value = Math.Min(value, max); value = Math.Min(value, max);
if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min) if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)

View File

@ -6,7 +6,7 @@
internal BoolLayerProperty() internal BoolLayerProperty()
{ {
KeyframesSupported = false; KeyframesSupported = false;
RegisterDataBindingProperty(b => b, new GeneralDataBindingConverter<bool>()); RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
} }
/// <summary> /// <summary>

View File

@ -1,17 +1,43 @@
namespace Artemis.Core using System.ComponentModel;
using SkiaSharp;
namespace Artemis.Core
{ {
/// <inheritdoc /> /// <inheritdoc />
public class ColorGradientLayerProperty : LayerProperty<ColorGradient> public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
{ {
private ColorGradient? _subscribedGradient;
internal ColorGradientLayerProperty() internal ColorGradientLayerProperty()
{ {
KeyframesSupported = false; KeyframesSupported = false;
DataBindingsSupported = false; DataBindingsSupported = true;
DefaultValue = new ColorGradient(); DefaultValue = new ColorGradient();
CurrentValueSet += OnCurrentValueSet; 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}");
}
}
/// <summary> /// <summary>
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" /> /// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
/// </summary> /// </summary>
@ -31,6 +57,22 @@
// Don't allow color gradients to be null // Don't allow color gradients to be null
if (BaseValue == null) if (BaseValue == null)
BaseValue = DefaultValue ?? new ColorGradient(); 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<ColorGradient> #region Overrides of LayerProperty<ColorGradient>
@ -47,4 +89,25 @@
#endregion #endregion
} }
internal class ColorStopDataBindingConverter : DataBindingConverter<ColorGradient, SKColor>
{
public ColorStopDataBindingConverter()
{
SupportsInterpolate = true;
SupportsSum = true;
}
/// <inheritdoc />
public override SKColor Sum(SKColor a, SKColor b)
{
return a.Sum(b);
}
/// <inheritdoc />
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
{
return a.Interpolate(b, (float) progress);
}
}
} }

View File

@ -5,7 +5,7 @@
{ {
internal FloatLayerProperty() internal FloatLayerProperty()
{ {
RegisterDataBindingProperty(value => value, new FloatDataBindingConverter()); RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
} }
/// <summary> /// <summary>

View File

@ -5,8 +5,8 @@
{ {
internal FloatRangeLayerProperty() internal FloatRangeLayerProperty()
{ {
RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter<FloatRange>()); RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter<FloatRange>(), "Start");
RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter<FloatRange>()); RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
CurrentValueSet += OnCurrentValueSet; CurrentValueSet += OnCurrentValueSet;
} }

View File

@ -7,7 +7,7 @@ namespace Artemis.Core
{ {
internal IntLayerProperty() internal IntLayerProperty()
{ {
RegisterDataBindingProperty(value => value, new IntDataBindingConverter()); RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
} }
/// <summary> /// <summary>

View File

@ -5,8 +5,8 @@
{ {
internal IntRangeLayerProperty() internal IntRangeLayerProperty()
{ {
RegisterDataBindingProperty(range => range.Start, new IntDataBindingConverter<IntRange>()); RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter<IntRange>(), "Start");
RegisterDataBindingProperty(range => range.End, new IntDataBindingConverter<IntRange>()); RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
CurrentValueSet += OnCurrentValueSet; CurrentValueSet += OnCurrentValueSet;
} }

View File

@ -7,7 +7,7 @@ namespace Artemis.Core
{ {
internal SKColorLayerProperty() internal SKColorLayerProperty()
{ {
RegisterDataBindingProperty(value => value, new SKColorDataBindingConverter()); RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
} }
/// <summary> /// <summary>

View File

@ -7,8 +7,8 @@ namespace Artemis.Core
{ {
internal SKPointLayerProperty() internal SKPointLayerProperty()
{ {
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter<SKPoint>()); RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter<SKPoint>(), "X");
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter<SKPoint>()); RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter<SKPoint>(), "Y");
} }
/// <summary> /// <summary>

View File

@ -7,8 +7,8 @@ namespace Artemis.Core
{ {
internal SKSizeLayerProperty() internal SKSizeLayerProperty()
{ {
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter<SKSize>()); RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter<SKSize>(), "Width");
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>()); RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter<SKSize>(), "Height");
} }
/// <summary> /// <summary>

View File

@ -11,9 +11,8 @@ namespace Artemis.Core
public class DataModelConditionEvent : DataModelConditionPart public class DataModelConditionEvent : DataModelConditionPart
{ {
private bool _disposed; private bool _disposed;
private IDataModelEvent? _event;
private bool _eventTriggered;
private bool _reinitializing; private bool _reinitializing;
private DateTime _lastTrigger;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class /// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
@ -53,22 +52,21 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionEvent"); throw new ObjectDisposedException("DataModelConditionEvent");
// Ensure the event has not been replaced if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent)
if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent) return false;
SubscribeToDataModelEvent(dataModelEvent); // Only evaluate to true once every time the event has been triggered since the last evaluation
if (dataModelEvent.LastTrigger <= _lastTrigger)
// Only evaluate to true once every time the event has been triggered
if (!_eventTriggered)
return false; return false;
_eventTriggered = false; _lastTrigger = DateTime.Now;
// If there is a child (root group), it must evaluate to true whenever the event triggered // If there is a child (root group), it must evaluate to true whenever the event triggered
if (Children.Any()) 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 // If there are no children, we always evaluate to true whenever the event triggered
return true; return true;
} }
/// <summary> /// <summary>
@ -172,16 +170,9 @@ namespace Artemis.Core
Entity.Children.Clear(); Entity.Children.Clear();
AddChild(new DataModelConditionGroup(this)); AddChild(new DataModelConditionGroup(this));
} }
}
private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent) if (EventPath?.GetValue() is IDataModelEvent dataModelEvent)
{ _lastTrigger = dataModelEvent.LastTrigger;
if (_event != null)
_event.EventTriggered -= OnEventTriggered;
_event = dataModelEvent;
if (_event != null)
_event.EventTriggered += OnEventTriggered;
} }
private Type? GetEventArgumentType() private Type? GetEventArgumentType()
@ -212,11 +203,6 @@ namespace Artemis.Core
#region Event handlers #region Event handlers
private void OnEventTriggered(object? sender, EventArgs e)
{
_eventTriggered = true;
}
private void EventPathOnPathValidated(object? sender, EventArgs e) private void EventPathOnPathValidated(object? sender, EventArgs e)
{ {
if (_reinitializing) if (_reinitializing)

View File

@ -97,7 +97,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public Type? GetTargetType() public Type? GetTargetType()
{ {
return Registration?.PropertyExpression.ReturnType; return Registration?.Getter.Method.ReturnType;
} }
private void ResetEasing(TProperty value) private void ResetEasing(TProperty value)
@ -272,7 +272,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("DataBinding"); throw new ObjectDisposedException("DataBinding");
// General // General
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression); DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
if (registration != null) if (registration != null)
ApplyRegistration(registration); ApplyRegistration(registration);
@ -293,7 +293,7 @@ namespace Artemis.Core
// Don't save an invalid state // Don't save an invalid state
if (Registration != null) if (Registration != null)
Entity.TargetExpression = Registration.PropertyExpression.ToString(); Entity.Identifier = Registration.DisplayName;
Entity.EasingTime = EasingTime; Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction; Entity.EasingFunction = (int) EasingFunction;

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -10,21 +8,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter
{ {
/// <summary>
/// Gets a dynamically compiled getter pointing to the data bound property
/// </summary>
public Func<TLayerProperty, TProperty>? GetExpression { get; private set; }
/// <summary>
/// Gets a dynamically compiled setter pointing to the data bound property used for value types
/// </summary>
public Action<TProperty>? ValueTypeSetExpression { get; private set; }
/// <summary>
/// Gets a dynamically compiled setter pointing to the data bound property used for reference types
/// </summary>
public Action<TLayerProperty, TProperty>? ReferenceTypeSetExpression { get; private set; }
/// <summary> /// <summary>
/// Gets the data binding this converter is applied to /// Gets the data binding this converter is applied to
/// </summary> /// </summary>
@ -40,9 +23,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
public bool SupportsInterpolate { get; protected set; } public bool SupportsInterpolate { get; protected set; }
/// <inheritdoc />
public Type SupportedType => typeof(TProperty);
/// <summary> /// <summary>
/// Returns the sum of <paramref name="a" /> and <paramref name="b" /> /// Returns the sum of <paramref name="a" /> and <paramref name="b" />
/// </summary> /// </summary>
@ -65,12 +45,9 @@ namespace Artemis.Core
/// <param name="value"></param> /// <param name="value"></param>
public virtual void ApplyValue(TProperty value) public virtual void ApplyValue(TProperty value)
{ {
if (DataBinding == null) if (DataBinding?.Registration == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized"); throw new ArtemisCoreException("Data binding converter is not yet initialized");
if (ReferenceTypeSetExpression != null) DataBinding.Registration.Setter(value);
ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value);
else if (ValueTypeSetExpression != null)
ValueTypeSetExpression(value);
} }
/// <summary> /// <summary>
@ -78,9 +55,9 @@ namespace Artemis.Core
/// </summary> /// </summary>
public virtual TProperty GetValue() public virtual TProperty GetValue()
{ {
if (DataBinding == null || GetExpression == null) if (DataBinding?.Registration == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized"); throw new ArtemisCoreException("Data binding converter is not yet initialized");
return GetExpression(DataBinding.LayerProperty.CurrentValue); return DataBinding.Registration.Getter();
} }
/// <summary> /// <summary>
@ -104,83 +81,10 @@ namespace Artemis.Core
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration"); throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
DataBinding = dataBinding; DataBinding = dataBinding;
GetExpression = dataBinding.Registration.PropertyExpression.Compile();
CreateSetExpression();
OnInitialized(); OnInitialized();
} }
private void CreateSetExpression() /// <inheritdoc />
{ public Type SupportedType => typeof(TProperty);
// If the registration does not point towards a member of LayerProperty<T>.CurrentValue, assign directly to LayerProperty<T>.CurrentValue
if (DataBinding!.Registration?.Member == null)
{
CreateSetCurrentValueExpression();
return;
}
// Ensure the member of LayerProperty<T>.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<T>.CurrentValue is a value type, assign it directly to LayerProperty<T>.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<Action<TLayerProperty, TProperty>> referenceTypeLambda = Expression.Lambda<Action<TLayerProperty, TProperty>>(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<Action<TProperty>> valueTypeLambda = Expression.Lambda<Action<TProperty>>(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<Action<TProperty>> lambda = Expression.Lambda<Action<TProperty>>(body, propertyValue);
ValueTypeSetExpression = lambda.Compile();
}
} }
} }

View File

@ -1,7 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Artemis.Storage.Entities.Profile.DataBindings; using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core namespace Artemis.Core
@ -9,16 +7,14 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
{ {
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, DataBindingConverter<TLayerProperty, TProperty> converter,
DataBindingConverter<TLayerProperty, TProperty> converter, Func<TProperty> getter, Action<TProperty> setter, string displayName)
Expression<Func<TLayerProperty, TProperty>> propertyExpression)
{ {
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
Converter = converter ?? throw new ArgumentNullException(nameof(converter)); Converter = converter ?? throw new ArgumentNullException(nameof(converter));
PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression)); Getter = getter ?? throw new ArgumentNullException(nameof(getter));
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
if (propertyExpression.Body is MemberExpression memberExpression) DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
Member = memberExpression.Member;
} }
/// <summary> /// <summary>
@ -32,15 +28,17 @@ namespace Artemis.Core
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; } public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
/// <summary> /// <summary>
/// Gets the expression that that accesses the property /// Gets the function to call to get the value of the property
/// </summary> /// </summary>
public Expression<Func<TLayerProperty, TProperty>> PropertyExpression { get; } public Func<TProperty> Getter { get; }
/// <summary> /// <summary>
/// Gets the member the <see cref="PropertyExpression" /> targets /// Gets the action to call to set the value of the property
/// <para><see langword="null"/> if the <see cref="PropertyExpression" /> is not a member expression</para>
/// </summary> /// </summary>
public MemberInfo? Member { get; } public Action<TProperty> Setter { get; }
/// <inheritdoc />
public string DisplayName { get; }
/// <summary> /// <summary>
/// Gets the data binding created using this registration /// Gets the data binding created using this registration
@ -59,7 +57,7 @@ namespace Artemis.Core
if (DataBinding != null) if (DataBinding != null)
return DataBinding; 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) if (dataBinding == null)
return null; return null;

View File

@ -5,6 +5,11 @@
/// </summary> /// </summary>
public interface IDataBindingRegistration public interface IDataBindingRegistration
{ {
/// <summary>
/// Gets or sets the display name of the data binding registration
/// </summary>
string DisplayName { get; }
/// <summary> /// <summary>
/// Returns the data binding applied using this registration /// Returns the data binding applied using this registration
/// </summary> /// </summary>

View File

@ -213,9 +213,25 @@ namespace Artemis.Core
Expression? expression = Expression.Convert(parameter, Target.GetType()); Expression? expression = Expression.Convert(parameter, Target.GetType());
Expression? nullCondition = null; Expression? nullCondition = null;
MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!;
foreach (DataModelPathSegment segment in _segments) 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; nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
expression = segment.Initialize(parameter, expression, nullCondition); expression = segment.Initialize(parameter, expression, nullCondition);
if (expression == null) if (expression == null)

View File

@ -97,6 +97,16 @@ namespace Artemis.Core
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved; public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <summary>
/// Occurs when a data binding property has been added
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <summary>
/// Occurs when all data binding properties have been removed
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <summary> /// <summary>
/// Occurs when a data binding has been enabled /// Occurs when a data binding has been enabled
/// </summary> /// </summary>

View File

@ -379,21 +379,16 @@ namespace Artemis.Core
public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null); public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null);
/// <summary> /// <summary>
/// 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
/// <para>Note: The expression must exactly match the one used to register the data binding</para> /// <para>Note: The expression must exactly match the one used to register the data binding</para>
/// </summary> /// </summary>
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(Expression<Func<T, TProperty>> propertyExpression) public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string identifier)
{
return GetDataBindingRegistration<TProperty>(propertyExpression.ToString());
}
internal DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string expression)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration && IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
registration.PropertyExpression.ToString() == expression); registration.DisplayName == identifier);
return (DataBindingRegistration<T, TProperty>?) match; return (DataBindingRegistration<T, TProperty>?) match;
} }
@ -410,21 +405,35 @@ namespace Artemis.Core
/// Registers a data binding property so that is available to the data binding system /// Registers a data binding property so that is available to the data binding system
/// </summary> /// </summary>
/// <typeparam name="TProperty">The type of the layer property</typeparam> /// <typeparam name="TProperty">The type of the layer property</typeparam>
/// <param name="propertyExpression">The expression pointing to the value to register</param> /// <param name="getter">The function to call to get the value of the property</param>
/// <param name="setter">The action to call to set the value of the property</param>
/// <param name="converter">The converter to use while applying the data binding</param> /// <param name="converter">The converter to use while applying the data binding</param>
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter) /// <param name="displayName">The display name of the data binding property</param>
public DataBindingRegistration<T, TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty> setter, DataBindingConverter<T, TProperty> 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<T, TProperty> registration = new(this, converter, getter, setter, displayName);
_dataBindingRegistrations.Add(registration);
OnDataBindingPropertyRegistered();
return registration;
}
/// <summary>
/// Removes all data binding properties so they are no longer available to the data binding system
/// </summary>
public void ClearDataBindingProperties()
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter) _dataBindingRegistrations.Clear();
throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'"); OnDataBindingPropertiesCleared();
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<T, TProperty>(this, converter, propertyExpression));
} }
/// <summary> /// <summary>
@ -661,6 +670,12 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved; public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled; public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
@ -716,6 +731,22 @@ namespace Artemis.Core
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
} }
/// <summary>
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
/// </summary>
protected virtual void OnDataBindingPropertyRegistered()
{
DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingPropertiesCleared()
{
DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary> /// <summary>
/// Invokes the <see cref="DataBindingEnabled" /> event /// Invokes the <see cref="DataBindingEnabled" /> event
/// </summary> /// </summary>

View File

@ -51,12 +51,6 @@ namespace Artemis.Core.DeviceProviders
/// </summary> /// </summary>
public bool CanDetectLogicalLayout { get; protected set; } public bool CanDetectLogicalLayout { get; protected set; }
/// <inheritdoc />
public override void Disable()
{
// Does not happen with device providers, they require Artemis to restart
}
/// <summary> /// <summary>
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" /> /// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary> /// </summary>

View File

@ -178,12 +178,15 @@ namespace Artemis.Core
{ {
foreach (PluginFeature feature in Features) foreach (PluginFeature feature in Features)
feature.Dispose(); feature.Dispose();
SetEnabled(false);
Kernel?.Dispose(); Kernel?.Dispose();
PluginLoader?.Dispose(); PluginLoader?.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
_features.Clear(); _features.Clear();
SetEnabled(false);
} }
} }

View File

@ -73,10 +73,10 @@ namespace Artemis.Core
if (!enable) if (!enable)
{ {
IsEnabled = false;
// Even if disable failed, still leave it in a disabled state to avoid more issues // Even if disable failed, still leave it in a disabled state to avoid more issues
InternalDisable(); InternalDisable();
IsEnabled = false;
OnDisabled(); OnDisabled();
return; return;
} }
@ -129,7 +129,8 @@ namespace Artemis.Core
internal virtual void InternalDisable() internal virtual void InternalDisable()
{ {
Disable(); if (IsEnabled)
Disable();
} }
#region IDisposable #region IDisposable
@ -145,7 +146,7 @@ namespace Artemis.Core
{ {
if (disposing) if (disposing)
{ {
Disable(); InternalDisable();
} }
} }

View File

@ -70,6 +70,12 @@ namespace Artemis.Core.Services
/// <returns>The resulting plugin</returns> /// <returns>The resulting plugin</returns>
Plugin ImportPlugin(string fileName); Plugin ImportPlugin(string fileName);
/// <summary>
/// Unloads and permanently removes the provided plugin
/// </summary>
/// <param name="plugin">The plugin to remove</param>
void RemovePlugin(Plugin plugin);
/// <summary> /// <summary>
/// Enables the provided plugin feature /// Enables the provided plugin feature
/// </summary> /// </summary>

View File

@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
@ -275,6 +276,7 @@ namespace Artemis.Core.Services
plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure => plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure =>
{ {
configure.IsUnloadable = true; configure.IsUnloadable = true;
configure.LoadInMemory = true;
configure.PreferSharedTypes = true; configure.PreferSharedTypes = true;
}); });
@ -430,7 +432,6 @@ namespace Artemis.Core.Services
OnPluginDisabled(new PluginEventArgs(plugin)); OnPluginDisabled(new PluginEventArgs(plugin));
} }
/// <inheritdoc />
public Plugin ImportPlugin(string fileName) public Plugin ImportPlugin(string fileName)
{ {
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins")); 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); Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
if (existing != null) 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 targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", "");
string uniqueTargetDirectory = targetDirectory; string uniqueTargetDirectory = targetDirectory;
@ -464,19 +474,38 @@ namespace Artemis.Core.Services
// Extract everything in the same archive directory to the unique plugin directory // Extract everything in the same archive directory to the unique plugin directory
DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory)); DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory));
Directory.CreateDirectory(directoryInfo.FullName); Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, ""); string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries) foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
{
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory)) if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory))
{ {
string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length)); 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 // Load the newly extracted plugin and return the result
return LoadPlugin(directoryInfo); return LoadPlugin(directoryInfo);
} }
public void RemovePlugin(Plugin plugin)
{
DirectoryInfo directory = plugin.Directory;
lock (_plugins)
{
if (_plugins.Contains(plugin))
UnloadPlugin(plugin);
}
directory.Delete(true);
}
#endregion #endregion
#region Features #region Features

View File

@ -71,7 +71,7 @@ namespace Artemis.Core.Services
_modifyingProviders = true; _modifyingProviders = true;
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); List<ArtemisDevice> 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) foreach (ArtemisDevice device in toRemove)
RemoveDevice(device); RemoveDevice(device);
@ -115,7 +115,7 @@ namespace Artemis.Core.Services
_modifyingProviders = true; _modifyingProviders = true;
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); List<ArtemisDevice> 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) foreach (ArtemisDevice device in toRemove)
RemoveDevice(device); RemoveDevice(device);

View File

@ -79,6 +79,11 @@ namespace Artemis.Core.Services.Models
public void Arrange(List<ArtemisDevice> devices) public void Arrange(List<ArtemisDevice> devices)
{ {
ArrangedDevices.Clear(); ArrangedDevices.Clear();
// Not much to do here
if (!devices.Any())
return;
foreach (ArtemisDevice surfaceDevice in devices) foreach (ArtemisDevice surfaceDevice in devices)
{ {
surfaceDevice.X = 0; surfaceDevice.X = 0;

View File

@ -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
{
/// <summary>
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
/// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
{
private readonly Module<T>? _module;
private readonly DataModelExpansion<T>? _dataModelExpansion;
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
_module = module ?? throw new ArgumentNullException(nameof(module));
ThrowOnFail = true;
Accepts = MimeType.Json;
}
internal DataModelJsonPluginEndPoint(DataModelExpansion<T> dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule)
{
_dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion));
ThrowOnFail = true;
Accepts = MimeType.Json;
}
/// <summary>
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
/// JSON throws a <see cref="JsonException" />.
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
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
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides data about endpoint exception related events
/// </summary>
public class EndpointExceptionEventArgs : EventArgs
{
internal EndpointExceptionEventArgs(Exception exception)
{
Exception = exception;
}
/// <summary>
/// Gets the exception that occurred
/// </summary>
public Exception Exception { get; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using EmbedIO;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides data about endpoint request related events
/// </summary>
public class EndpointRequestEventArgs : EventArgs
{
internal EndpointRequestEventArgs(IHttpContext context)
{
Context = context;
}
/// <summary>
/// Gets the HTTP context of the request
/// </summary>
public IHttpContext Context { get; }
}
}

View File

@ -52,15 +52,65 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
public string? Returns { get; protected set; } public string? Returns { get; protected set; }
/// <summary>
/// Occurs whenever a request threw an unhandled exception
/// </summary>
public event EventHandler<EndpointExceptionEventArgs>? RequestException;
/// <summary>
/// Occurs whenever a request is about to be processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessingRequest;
/// <summary>
/// Occurs whenever a request was processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessedRequest;
/// <summary> /// <summary>
/// Called whenever the end point has to process a request /// Called whenever the end point has to process a request
/// </summary> /// </summary>
/// <param name="context">The HTTP context of the request</param> /// <param name="context">The HTTP context of the request</param>
protected abstract Task ProcessRequest(IHttpContext context); protected abstract Task ProcessRequest(IHttpContext context);
/// <summary>
/// Invokes the <see cref="RequestException" /> event
/// </summary>
/// <param name="e">The exception that occurred during the request</param>
protected virtual void OnRequestException(Exception e)
{
RequestException?.Invoke(this, new EndpointExceptionEventArgs(e));
}
/// <summary>
/// Invokes the <see cref="ProcessingRequest" /> event
/// </summary>
protected virtual void OnProcessingRequest(IHttpContext context)
{
ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
/// <summary>
/// Invokes the <see cref="ProcessedRequest" /> event
/// </summary>
protected virtual void OnProcessedRequest(IHttpContext context)
{
ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
internal async Task InternalProcessRequest(IHttpContext 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) private void OnDisabled(object? sender, EventArgs e)

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using EmbedIO.WebApi; using EmbedIO.WebApi;
@ -43,6 +45,24 @@ namespace Artemis.Core.Services
/// <returns>The resulting end point</returns> /// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler); JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
/// </summary>
/// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="dataModelExpansion" />.
/// </summary>
/// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel;
/// <summary> /// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />. /// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary> /// </summary>

View File

@ -1,10 +1,7 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using EmbedIO; using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services namespace Artemis.Core.Services
{ {
@ -67,6 +64,12 @@ namespace Artemis.Core.Services
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint)) if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}."); 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 // It is up to the registration how the request is eventually handled, it might even set a response here
await endPoint.InternalProcessRequest(context); await endPoint.InternalProcessRequest(context);

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using EmbedIO.WebApi; using EmbedIO.WebApi;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -28,7 +30,6 @@ namespace Artemis.Core.Services
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
PluginsModule = new PluginsModule("/plugins"); PluginsModule = new PluginsModule("/plugins");
StartWebServer(); StartWebServer();
} }
@ -42,7 +43,7 @@ namespace Artemis.Core.Services
Server?.Dispose(); Server?.Dispose();
Server = null; Server = null;
string url = $"http://localhost:{_webServerPortSetting.Value}/"; string url = $"http://*:{_webServerPortSetting.Value}/";
WebApiModule apiModule = new("/api/", JsonNetSerializer); WebApiModule apiModule = new("/api/", JsonNetSerializer);
PluginsModule.ServerUrl = url; PluginsModule.ServerUrl = url;
WebServer server = new WebServer(o => o.WithUrlPrefix(url).WithMode(HttpListenerMode.EmbedIO)) WebServer server = new WebServer(o => o.WithUrlPrefix(url).WithMode(HttpListenerMode.EmbedIO))
@ -126,6 +127,29 @@ namespace Artemis.Core.Services
return endPoint; return endPoint;
} }
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel
{
if (module == null) throw new ArgumentNullException(nameof(module));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
DataModelJsonPluginEndPoint<T> endPoint = new(module, endPointName, PluginsModule);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel
{
if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
DataModelJsonPluginEndPoint<T> endPoint = new(dataModelExpansion, endPointName, PluginsModule);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel
{
}
public void RemovePluginEndPoint(PluginEndPoint endPoint) public void RemovePluginEndPoint(PluginEndPoint endPoint)
{ {
PluginsModule.RemovePluginEndPoint(endPoint); PluginsModule.RemovePluginEndPoint(endPoint);

View File

@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings
{ {
public class DataBindingEntity public class DataBindingEntity
{ {
public string TargetExpression { get; set; } public string Identifier { get; set; }
public TimeSpan EasingTime { get; set; } public TimeSpan EasingTime { get; set; }
public int EasingFunction { get; set; } public int EasingFunction { get; set; }

View File

@ -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<BsonDocument> 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);
}
}
}
}

View File

@ -132,7 +132,7 @@
<PackageReference Include="FluentValidation" Version="9.3.0" /> <PackageReference Include="FluentValidation" Version="9.3.0" />
<PackageReference Include="Flurl.Http" Version="3.0.1" /> <PackageReference Include="Flurl.Http" Version="3.0.1" />
<PackageReference Include="gong-wpf-dragdrop" Version="2.3.2" /> <PackageReference Include="gong-wpf-dragdrop" Version="2.3.2" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.14" /> <PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.18" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0-a01" /> <PackageReference Include="MaterialDesignExtensions" Version="3.3.0-a01" />
<PackageReference Include="MaterialDesignThemes" Version="3.2.0" /> <PackageReference Include="MaterialDesignThemes" Version="3.2.0" />

View File

@ -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;
}
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.PropertyInput.BoolPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.BoolPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -1,15 +1,25 @@
using Artemis.Core; using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class BoolPropertyInputViewModel : PropertyInputViewModel<bool> public class BoolPropertyInputViewModel : PropertyInputViewModel<bool>
{ {
private List<IDataBindingRegistration> _registrations;
public BoolPropertyInputViewModel(LayerProperty<bool> layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) public BoolPropertyInputViewModel(LayerProperty<bool> 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));
}
} }
} }

View File

@ -1,12 +1,12 @@
<UserControl x:Class="Artemis.UI.PropertyInput.BrushPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.BrushPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
xmlns:layerBrush="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core" xmlns:layerBrush="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}"> d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}">

View File

@ -7,7 +7,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference> public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
{ {

View File

@ -1,9 +1,8 @@
<UserControl x:Class="Artemis.UI.PropertyInput.ColorGradientPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"

View File

@ -3,7 +3,7 @@ using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorGradient> public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorGradient>
{ {

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.PropertyInput.EnumPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.EnumPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -4,7 +4,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class EnumPropertyInputViewModel<T> : PropertyInputViewModel<T> where T : Enum public class EnumPropertyInputViewModel<T> : PropertyInputViewModel<T> where T : Enum
{ {

View File

@ -1,11 +1,10 @@
<UserControl x:Class="Artemis.UI.PropertyInput.FloatPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.FloatPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"

View File

@ -4,7 +4,7 @@ using Artemis.UI.Shared.Services;
using FluentValidation; using FluentValidation;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class FloatPropertyInputViewModel : PropertyInputViewModel<float> public class FloatPropertyInputViewModel : PropertyInputViewModel<float>
{ {
@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput
public FloatPropertyInputViewModel(LayerProperty<float> layerProperty, IProfileEditorService profileEditorService, IModelValidator<FloatPropertyInputViewModel> validator) public FloatPropertyInputViewModel(LayerProperty<float> layerProperty, IProfileEditorService profileEditorService, IModelValidator<FloatPropertyInputViewModel> validator)
: base(layerProperty, profileEditorService, validator) : base(layerProperty, profileEditorService, validator)
{ {
_registration = layerProperty.GetDataBindingRegistration(value => value); _registration = layerProperty.GetDataBindingRegistration<float>("Value");
} }
public bool IsEnabled => _registration.DataBinding == null; public bool IsEnabled => _registration.DataBinding == null;

View File

@ -1,9 +1,8 @@
<UserControl x:Class="Artemis.UI.PropertyInput.FloatRangePropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.FloatRangePropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"

View File

@ -5,7 +5,7 @@ using Artemis.UI.Shared.Services;
using FluentValidation; using FluentValidation;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class FloatRangePropertyInputViewModel : PropertyInputViewModel<FloatRange> public class FloatRangePropertyInputViewModel : PropertyInputViewModel<FloatRange>
{ {
@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IModelValidator<FloatRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator) IModelValidator<FloatRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{ {
_startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); _startRegistration = layerProperty.GetDataBindingRegistration<float>("Start");
_endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); _endRegistration = layerProperty.GetDataBindingRegistration<float>("End");
} }
public float Start public float Start

View File

@ -1,11 +1,10 @@
<UserControl x:Class="Artemis.UI.PropertyInput.IntPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.IntPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"

View File

@ -4,7 +4,7 @@ using Artemis.UI.Shared.Services;
using FluentValidation; using FluentValidation;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class IntPropertyInputViewModel : PropertyInputViewModel<int> public class IntPropertyInputViewModel : PropertyInputViewModel<int>
{ {
@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput
public IntPropertyInputViewModel(LayerProperty<int> layerProperty, IProfileEditorService profileEditorService, IModelValidator<IntPropertyInputViewModel> validator) public IntPropertyInputViewModel(LayerProperty<int> layerProperty, IProfileEditorService profileEditorService, IModelValidator<IntPropertyInputViewModel> validator)
: base(layerProperty, profileEditorService, validator) : base(layerProperty, profileEditorService, validator)
{ {
_registration = layerProperty.GetDataBindingRegistration(value => value); _registration = layerProperty.GetDataBindingRegistration<int>("Value");
} }
public bool IsEnabled => _registration.DataBinding == null; public bool IsEnabled => _registration.DataBinding == null;

View File

@ -1,9 +1,8 @@
<UserControl x:Class="Artemis.UI.PropertyInput.IntRangePropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.IntRangePropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"

View File

@ -5,7 +5,7 @@ using Artemis.UI.Shared.Services;
using FluentValidation; using FluentValidation;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class IntRangePropertyInputViewModel : PropertyInputViewModel<IntRange> public class IntRangePropertyInputViewModel : PropertyInputViewModel<IntRange>
{ {
@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IModelValidator<IntRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator) IModelValidator<IntRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{ {
_startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); _startRegistration = layerProperty.GetDataBindingRegistration<int>("Start");
_endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); _endRegistration = layerProperty.GetDataBindingRegistration<int>("End");
} }
public int Start public int Start

View File

@ -1,10 +1,9 @@
<UserControl x:Class="Artemis.UI.PropertyInput.SKColorPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"

View File

@ -3,7 +3,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class SKColorPropertyInputViewModel : PropertyInputViewModel<SKColor> public class SKColorPropertyInputViewModel : PropertyInputViewModel<SKColor>
{ {
@ -11,7 +11,7 @@ namespace Artemis.UI.PropertyInput
public SKColorPropertyInputViewModel(LayerProperty<SKColor> layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) public SKColorPropertyInputViewModel(LayerProperty<SKColor> layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService)
{ {
_registration = layerProperty.GetDataBindingRegistration(value => value); _registration = layerProperty.GetDataBindingRegistration<SKColor>("Value");
} }
public bool IsEnabled => _registration.DataBinding == null; public bool IsEnabled => _registration.DataBinding == null;

View File

@ -1,11 +1,10 @@
<UserControl x:Class="Artemis.UI.PropertyInput.SKPointPropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKPointPropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance propertyInput:SKPointPropertyInputViewModel}"> d:DataContext="{d:DesignInstance propertyInput:SKPointPropertyInputViewModel}">

View File

@ -6,7 +6,7 @@ using FluentValidation;
using SkiaSharp; using SkiaSharp;
using Stylet; using Stylet;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class SKPointPropertyInputViewModel : PropertyInputViewModel<SKPoint> public class SKPointPropertyInputViewModel : PropertyInputViewModel<SKPoint>
{ {
@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput
public SKPointPropertyInputViewModel(LayerProperty<SKPoint> layerProperty, IProfileEditorService profileEditorService, public SKPointPropertyInputViewModel(LayerProperty<SKPoint> layerProperty, IProfileEditorService profileEditorService,
IModelValidator<SKPointPropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator) IModelValidator<SKPointPropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{ {
_xRegistration = layerProperty.GetDataBindingRegistration(point => point.X); _xRegistration = layerProperty.GetDataBindingRegistration<float>("X");
_yRegistration = layerProperty.GetDataBindingRegistration(point => point.Y); _yRegistration = layerProperty.GetDataBindingRegistration<float>("Y");
} }
public float X public float X

View File

@ -1,9 +1,8 @@
<UserControl x:Class="Artemis.UI.PropertyInput.SKSizePropertyInputView" <UserControl x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKSizePropertyInputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"

View File

@ -8,7 +8,7 @@ using Stylet;
// using PropertyChanged; // using PropertyChanged;
namespace Artemis.UI.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
public class SKSizePropertyInputViewModel : PropertyInputViewModel<SKSize> public class SKSizePropertyInputViewModel : PropertyInputViewModel<SKSize>
{ {
@ -18,8 +18,8 @@ namespace Artemis.UI.PropertyInput
public SKSizePropertyInputViewModel(LayerProperty<SKSize> layerProperty, IProfileEditorService profileEditorService, public SKSizePropertyInputViewModel(LayerProperty<SKSize> layerProperty, IProfileEditorService profileEditorService,
IModelValidator<SKSizePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator) IModelValidator<SKSizePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{ {
_widthRegistration = layerProperty.GetDataBindingRegistration(size => size.Width); _widthRegistration = layerProperty.GetDataBindingRegistration<float>("Width");
_heightRegistration = layerProperty.GetDataBindingRegistration(size => size.Height); _heightRegistration = layerProperty.GetDataBindingRegistration<float>("Height");
} }
// Since SKSize is immutable we need to create properties that replace the SKSize entirely // Since SKSize is immutable we need to create properties that replace the SKSize entirely

View File

@ -15,6 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization;
using Artemis.UI.Screens.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.ProfileEditor.Visualization.Tools;
using Artemis.UI.Screens.Settings.Debug; using Artemis.UI.Screens.Settings.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.Devices;
using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Screens.Settings.Tabs.Plugins;
using Artemis.UI.Screens.Shared; using Artemis.UI.Screens.Shared;
@ -43,7 +45,10 @@ namespace Artemis.UI.Ninject.Factories
public interface IDeviceDebugVmFactory : IVmFactory 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 public interface IProfileTreeVmFactory : IVmFactory

View File

@ -200,7 +200,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<RadioButton Grid.Column="0" <RadioButton Grid.Column="0"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}"> IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" /> <materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
RESTART RESTART
@ -215,7 +215,7 @@
</RadioButton> </RadioButton>
<RadioButton Grid.Column="1" <RadioButton Grid.Column="1"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" /> <materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" />
IGNORE IGNORE
@ -230,7 +230,7 @@
</RadioButton> </RadioButton>
<RadioButton Grid.Column="2" <RadioButton Grid.Column="2"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" /> <materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" />
COPY COPY

View File

@ -38,7 +38,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
public RenderProfileElement RenderProfileElement public RenderProfileElement RenderProfileElement
{ {
get => _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 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; public bool ConditionBehaviourEnabled => RenderProfileElement != null;
protected override void OnInitialActivate() protected override void OnInitialActivate()
@ -119,5 +136,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
} }
public void EventTriggerModeSelected()
{
_profileEditorService.UpdateSelectedProfileElement();
}
} }
} }

View File

@ -38,11 +38,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_dataBindingsVmFactory = dataBindingsVmFactory; _dataBindingsVmFactory = dataBindingsVmFactory;
if (Registration.Member != null) DisplayName = Registration.DisplayName.ToUpper();
DisplayName = Registration.Member.Name.ToUpper();
else
DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper();
AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true);
DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType)));
EasingViewModels = new BindableCollection<TimelineEasingViewModel>(); EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
@ -106,8 +102,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
base.OnInitialActivate();
Initialize(); Initialize();
base.OnInitialActivate();
} }
private void Initialize() private void Initialize()

View File

@ -6,11 +6,18 @@
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<TabControl ItemsSource="{Binding Items}" SelectedIndex="{Binding SelectedItemIndex}" DisplayMemberPath="DisplayName" Style="{StaticResource MaterialDesignTabControl}"> <TabControl ItemsSource="{Binding Items}"
SelectedIndex="{Binding SelectedItemIndex}"
Style="{StaticResource MaterialDesignTabControl}" >
<TabControl.ContentTemplate> <TabControl.ContentTemplate>
<DataTemplate> <DataTemplate>
<ContentControl s:View.Model="{Binding}" TextElement.Foreground="{DynamicResource MaterialDesignBody}" /> <ContentControl s:View.Model="{Binding}" TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate> </DataTemplate>
</TabControl.ContentTemplate> </TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Margin="-12 0" Text="{Binding DisplayName}"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl> </TabControl>
</UserControl> </UserControl>

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -11,7 +14,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{ {
private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private ILayerProperty? _selectedDataBinding;
private int _selectedItemIndex; private int _selectedItemIndex;
private bool _updating;
public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory)
{ {
@ -27,6 +32,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private void CreateDataBindingViewModels() private void CreateDataBindingViewModels()
{ {
int oldIndex = SelectedItemIndex;
Items.Clear(); Items.Clear();
ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding; ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding;
@ -37,27 +43,65 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
// Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving
// and creating the actual data bindings // and creating the actual data bindings
foreach (IDataBindingRegistration registration in registrations) Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration)));
Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration));
SelectedItemIndex = 0; SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex;
} }
private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e)
{ {
CreateDataBindingViewModels(); 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 #region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
_profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
CreateDataBindingViewModels(); CreateDataBindingViewModels();
SubscribeToSelectedDataBinding();
base.OnInitialActivate(); base.OnInitialActivate();
} }
protected override void OnActivate()
{
SelectedItemIndex = 0;
base.OnActivate();
}
protected override void OnClose() protected override void OnClose()
{ {
_profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged; _profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged;

View File

@ -302,7 +302,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
TimeSpan delta = DateTime.Now - _lastUpdate; TimeSpan delta = DateTime.Now - _lastUpdate;
_lastUpdate = DateTime.Now; _lastUpdate = DateTime.Now;
if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null || _profileEditorService.Playing) if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null)
return; return;
foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders()

View File

@ -1,310 +0,0 @@
<mde:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Debug.DeviceDebugView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:debug="clr-namespace:Artemis.UI.Screens.Settings.Debug"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
Title="Artemis device debugger"
TitleBarIcon="{StaticResource BowIcon}"
Foreground="{DynamicResource MaterialDesignBody}"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
FadeContentIfInactive="False"
Width="840"
Height="800"
d:DesignHeight="800" d:DesignWidth="800" d:DataContext="{d:DesignInstance debug:DeviceDebugViewModel}"
Icon="/Resources/Images/Logo/logo-512.png">
<mde:MaterialWindow.InputBindings>
<KeyBinding Command="{s:Action ClearSelection}" Key="Escape" />
</mde:MaterialWindow.InputBindings>
<DockPanel>
<mde:AppBar Type="Dense"
Title="{Binding Device.RgbDevice.DeviceInfo.Model}"
ShowShadow="True"
DockPanel.Dock="Top"
Margin="-18 0 0 0">
<mde:AppBar.AppIcon>
<materialDesign:PackIcon Kind="HammerWrench" Width="20" Height="28" />
</mde:AppBar.AppIcon>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Identify" Command="{s:Action IdentifyDevice}">
<materialDesign:PackIcon Kind="AlarmLight" />
</Button>
<materialDesign:PopupBox PlacementMode="BottomAndAlignRightEdges" StaysOpen="False">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Plugin" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action OpenImageDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Image" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open layout image directory</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action ReloadLayout}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Reload" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Reload layout</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action ExportLayout}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Xml" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Export layout</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
</mde:AppBar>
<ScrollViewer>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Margin="0 0 0 10">
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.
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Padding="15" Margin="0 10">
<shared:DeviceVisualizer Device="{Binding Device}"
HighlightedLeds="{Binding SelectedLeds}"
HorizontalAlignment="Center"
MaxHeight="400"
ShowColors="True" />
</materialDesign:Card>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="2" Padding="15" Margin="0 10">
<Expander VerticalAlignment="Center" Header="Device properties">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="15" HorizontalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device name</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Manufacturer</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device type</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.DeviceType}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Physical layout</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.PhysicalLayout}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device image</TextBlock>
<TextBox Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.Layout.Image, Mode=OneWay}"
IsReadOnly="True" />
</StackPanel>
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Margin="15" HorizontalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Size (1px = 1mm)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Size}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Location (1px = 1mm)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Location}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Rotation (degrees)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Rotation.Degrees}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Logical layout</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.LogicalLayout}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Layout file path</TextBlock>
<TextBox Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.Layout.FilePath, Mode=OneWay}"
IsReadOnly="True" />
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</Expander>
</materialDesign:Card>
<materialDesign:Card Grid.Row="3" materialDesign:ShadowAssist.ShadowDepth="Depth1" Padding="15" MaxHeight="413" Margin="0 10">
<DataGrid ItemsSource="{Binding Device.Leds}"
d:DataContext="{d:DesignInstance Type=core:ArtemisLed}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
materialDesign:DataGridAssist.CellPadding="13 8 8 8"
materialDesign:DataGridAssist.ColumnHeaderPadding="8"
SelectedItem="{Binding SelectedLed}"
CanUserResizeRows="False">
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Id}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Color}" Header="Color (ARGB)" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Image}" Header="Image path" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Shape}" Header="Shape" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Size}" Header="Size" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</materialDesign:Card>
</Grid>
</ScrollViewer>
</DockPanel>
</mde:MaterialWindow>

View File

@ -6,10 +6,10 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs" xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes" 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" xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" 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}">
<UserControl.Resources> <UserControl.Resources>
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter" /> <dataModel:TypeToStringConverter x:Key="TypeToStringConverter" />
</UserControl.Resources> </UserControl.Resources>
@ -19,6 +19,10 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0 0 0 5"> <Grid Grid.Row="0" Margin="0 0 0 5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -29,16 +33,18 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Property searching (nyi) --> <!-- Property searching (nyi) -->
<wpf:PackIcon Grid.Column="0" Kind="Search" VerticalAlignment="Center" /> <wpf:PackIcon Grid.Column="0" Kind="Search" VerticalAlignment="Center" />
<TextBox Grid.Column="1" wpf:HintAssist.Hint="Search property" VerticalAlignment="Center" Margin="5 0" IsEnabled="False" /> <TextBox Grid.Row="0" Grid.Column="1" wpf:HintAssist.Hint="Search property" VerticalAlignment="Center" Margin="5 0" IsEnabled="False" />
<!-- Module filtering --> <!-- Module filtering -->
<TextBlock Grid.Column="3" VerticalAlignment="Center">Filter module</TextBlock> <TextBlock Grid.Row="0" Grid.Column="3" VerticalAlignment="Center">Filter module</TextBlock>
<ToggleButton Grid.Column="4" <ToggleButton Grid.Row="0"
Grid.Column="4"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="5 0" Margin="5 0"
Style="{StaticResource MaterialDesignSwitchToggleButton}" Style="{StaticResource MaterialDesignSwitchToggleButton}"
IsChecked="{Binding IsModuleFilterEnabled}" /> IsChecked="{Binding IsModuleFilterEnabled}" />
<ComboBox Grid.Column="5" <ComboBox Grid.Row="0"
Grid.Column="5"
VerticalAlignment="Center" VerticalAlignment="Center"
wpf:HintAssist.Hint="Select a module" wpf:HintAssist.Hint="Select a module"
IsEditable="True" IsEditable="True"
@ -48,6 +54,17 @@
IsEnabled="{Binding IsModuleFilterEnabled}" IsEnabled="{Binding IsModuleFilterEnabled}"
SelectedItem="{Binding SelectedModule}" SelectedItem="{Binding SelectedModule}"
ItemsSource="{Binding Modules}" /> ItemsSource="{Binding Modules}" />
<StackPanel Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
Margin="0 10 0 0"
ToolTip="Check to update values every half second instead of realtime">
<TextBlock VerticalAlignment="Center">Slow updates</TextBlock>
<ToggleButton VerticalAlignment="Center" Margin="5 0" Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="{Binding SlowUpdates}" />
</StackPanel>
</Grid> </Grid>
<TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch"> <TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch">
<TreeView.Resources> <TreeView.Resources>

View File

@ -20,12 +20,13 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
private DataModelPropertiesViewModel _mainDataModel; private DataModelPropertiesViewModel _mainDataModel;
private string _propertySearch; private string _propertySearch;
private Module _selectedModule; private Module _selectedModule;
private bool _slowUpdates;
public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
{ {
_dataModelUIService = dataModelUIService; _dataModelUIService = dataModelUIService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_updateTimer = new Timer(500); _updateTimer = new Timer(25);
DisplayName = "Data model"; DisplayName = "Data model";
Modules = new BindableCollection<Module>(); Modules = new BindableCollection<Module>();
@ -43,6 +44,16 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
set => SetAndNotify(ref _propertySearch, value); set => SetAndNotify(ref _propertySearch, value);
} }
public bool SlowUpdates
{
get => _slowUpdates;
set
{
SetAndNotify(ref _slowUpdates, value);
_updateTimer.Interval = _slowUpdates ? 500 : 25;
}
}
public BindableCollection<Module> Modules { get; } public BindableCollection<Module> Modules { get; }
public Module SelectedModule public Module SelectedModule
@ -116,7 +127,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
lock (MainDataModel) lock (MainDataModel)
{ {
MainDataModel.Update(_dataModelUIService, null); MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
} }
} }

View File

@ -0,0 +1,133 @@
<mde:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Device.DeviceDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Settings.Device"
mc:Ignorable="d"
Title="{Binding DisplayName}"
TitleBarIcon="{StaticResource BowIcon}"
Foreground="{DynamicResource MaterialDesignBody}"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
FadeContentIfInactive="False"
Width="1400"
Height="800"
d:DesignHeight="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance device:DeviceDialogViewModel}"
Icon="/Resources/Images/Logo/logo-512.png">
<mde:MaterialWindow.InputBindings>
<KeyBinding Command="{s:Action ClearSelection}" Key="Escape" />
</mde:MaterialWindow.InputBindings>
<DockPanel>
<mde:AppBar Type="Dense"
Title="{Binding Device.RgbDevice.DeviceInfo.Model}"
ShowShadow="True"
DockPanel.Dock="Top"
Margin="-18 0 0 0">
<mde:AppBar.AppIcon>
<materialDesign:PackIcon Kind="HammerWrench" Width="20" Height="28" />
</mde:AppBar.AppIcon>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Identify" Command="{s:Action IdentifyDevice}">
<materialDesign:PackIcon Kind="AlarmLight" />
</Button>
<materialDesign:PopupBox PlacementMode="BottomAndAlignRightEdges" StaysOpen="False">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Plugin" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action OpenImageDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Image" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open layout image directory</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action ReloadLayout}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Reload" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Reload layout</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action ExportLayout}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Xml" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Export layout</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
</mde:AppBar>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Name="DeviceDisplayGrid" Grid.Column="0">
<Grid.Background>
<VisualBrush TileMode="Tile" Stretch="Uniform" Viewport="0 0 25 25" ViewportUnits="Absolute">
<VisualBrush.Visual>
<Grid Width="20" Height="20">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
<Rectangle Grid.Row="0" Grid.Column="1" />
<Rectangle Grid.Row="1" Grid.Column="0" />
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<shared:DeviceVisualizer Device="{Binding Device}"
HighlightedLeds="{Binding SelectedLeds}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ShowColors="True"
Margin="0 0 100 0" />
</Grid>
<GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch" Panel.ZIndex="3" />
<materialDesign:Card Grid.Column="2"
materialDesign:ShadowAssist.ShadowDepth="Depth3"
Background="{DynamicResource MaterialDesignPaper}">
<TabControl
Style="{StaticResource MaterialDesignTabControl}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="DisplayName">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"
TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</materialDesign:Card>
</Grid>
</DockPanel>
</mde:MaterialWindow>

View File

@ -2,35 +2,44 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Xml.Serialization; using System.Xml.Serialization;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Ookii.Dialogs.Wpf; using Ookii.Dialogs.Wpf;
using RGB.NET.Layout; using RGB.NET.Layout;
using Stylet; using Stylet;
// using PropertyChanged; namespace Artemis.UI.Screens.Settings.Device
namespace Artemis.UI.Screens.Settings.Debug
{ {
public class DeviceDebugViewModel : Screen public class DeviceDialogViewModel : Conductor<Screen>.Collection.OneActive
{ {
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private ArtemisLed _selectedLed; 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; _deviceService = deviceService;
_rgbService = rgbService; _rgbService = rgbService;
_dialogService = dialogService; _dialogService = dialogService;
Device = device; 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<ArtemisLed> SelectedLeds => SelectedLed != null ? new List<ArtemisLed> {SelectedLed} : null;
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public PanZoomViewModel PanZoomViewModel { get; }
public ArtemisLed SelectedLed public ArtemisLed SelectedLed
{ {
@ -41,6 +50,7 @@ namespace Artemis.UI.Screens.Settings.Debug
NotifyOfPropertyChange(nameof(SelectedLeds)); NotifyOfPropertyChange(nameof(SelectedLeds));
} }
} }
public List<ArtemisLed> SelectedLeds => SelectedLed != null ? new List<ArtemisLed> { SelectedLed } : null;
public bool CanOpenImageDirectory => Device.Layout?.Image != null; public bool CanOpenImageDirectory => Device.Layout?.Image != null;

View File

@ -0,0 +1,121 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceInfoTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance tabs:DeviceInfoTabViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- First row -->
<StackPanel HorizontalAlignment="Stretch" Grid.Column="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Top" Margin="15 15 7.5 0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device name</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Manufacturer</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device type</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.DeviceType}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<StackPanel Visibility="{Binding IsKeyboard, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Physical layout</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.PhysicalLayout}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Top" Margin="7.5 15 15 0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Size (1px = 1mm)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Size}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Location (1px = 1mm)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Location}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Rotation (degrees)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Rotation.Degrees}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<StackPanel Visibility="{Binding IsKeyboard, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Logical layout</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.LogicalLayout}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
</StackPanel>
</StackPanel>
<!-- Second row -->
<StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Margin="15 0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Layout file path</TextBlock>
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Copy path to clipboard" Width="24" Height="24">
<materialDesign:PackIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</Grid>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.Layout.FilePath}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Image file path</TextBlock>
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Copy path to clipboard" Width="24" Height="24">
<materialDesign:PackIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</Grid>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.Layout.Image}" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -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; }
}
}

View File

@ -0,0 +1,37 @@
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}">
<UserControl.Resources>
<Converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/>
</UserControl.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Device.Leds}"
d:DataContext="{d:DesignInstance Type={x:Type core:ArtemisLed}}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
materialDesign:DataGridAssist.CellPadding="13 8 8 8"
materialDesign:DataGridAssist.ColumnHeaderPadding="8"
SelectedItem="{Binding Parent.SelectedLed}"
CanUserResizeRows="False"
Margin="10">
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Id}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Color}" Header="Color (ARGB)" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Shape}" Header="Shape" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Size}" Header="Size" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>

View File

@ -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; }
}
}

View File

@ -0,0 +1,189 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DevicePropertiesTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DevicePropertiesTabViewModel}">
<UserControl.Resources>
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<!-- Body -->
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="40"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Surface properties
</TextBlock>
<TextBox materialDesign:HintAssist.Hint="X-coordinate"
materialDesign:TextFieldAssist.SuffixText="mm"
Text="{Binding X, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Y-coordinate"
materialDesign:TextFieldAssist.SuffixText="mm"
Text="{Binding Y, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Scale"
materialDesign:TextFieldAssist.SuffixText="times"
Text="{Binding Scale, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Rotation"
materialDesign:TextFieldAssist.SuffixText="deg"
Text="{Binding Rotation, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5 0 12" />
</StackPanel>
<Rectangle Grid.Column="1" VerticalAlignment="Stretch" Fill="{StaticResource MaterialDesignTextBoxBorder}" Width="1" Margin="0 0 0 5" />
<StackPanel Grid.Column="2">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Color calibration
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Use the sliders below to adjust the colors of your device so that it matches your other devices.
</TextBlock>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="R" VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding RedScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10"
VerticalAlignment="Center" />
<TextBox Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding RedScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
Width="50" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="G" VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding GreenScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10"
VerticalAlignment="Center" />
<TextBox Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding GreenScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
Width="50" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="B" VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding BlueScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Ticks="100"
VerticalAlignment="Center" />
<TextBox Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding BlueScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
Width="50" />
</Grid>
<Grid Margin="0 28 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding DisplayOnDevices}"
Content="Show preview"
VerticalAlignment="Center" />
<shared:ColorPicker Grid.Column="1"
Margin="0,0,5,0"
HorizontalAlignment="Right"
Color="{Binding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
VerticalAlignment="Center" />
</Grid>
</StackPanel>
</Grid>
<!-- Layout -->
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="0 25 0 0">
Custom layout
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Select a custom layout below if you want to change the appearance and/or LEDs of this device.
</TextBlock>
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding Device.CustomLayoutPath}"
VerticalAlignment="Center"
materialDesign:TextFieldAssist.HasClearButton="True"
IsReadOnly="True"
PreviewMouseLeftButtonUp="{s:Action BrowseCustomLayout}">
<materialDesign:HintAssist.Hint>
<StackPanel Orientation="Horizontal" Margin="-2 0 0 0">
<materialDesign:PackIcon Kind="Xml" Width="20" />
<TextBlock>Layout path</TextBlock>
</StackPanel>
</materialDesign:HintAssist.Hint>
</TextBox>
</StackPanel>
<!-- Buttons -->
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" IsCancel="True" Margin="0 8 8 0" Command="{s:Action Reset}">
RESET
</Button>
<Button Style="{StaticResource MaterialDesignRaisedButton}" IsDefault="True" Margin="0 8 8 0" Command="{s:Action Apply}">
APPLY
</Button>
</StackPanel>
</Grid>
</UserControl>

View File

@ -5,68 +5,46 @@ using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Ookii.Dialogs.Wpf; using Ookii.Dialogs.Wpf;
using SkiaSharp; using SkiaSharp;
using Stylet; 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 ICoreService _coreService;
private readonly IRgbService _rgbService;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private readonly float _initialRedScale; private readonly IRgbService _rgbService;
private readonly float _initialGreenScale; private double _blueScale;
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 SKColor _currentColor; private SKColor _currentColor;
private bool _displayOnDevices; 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, ICoreService coreService,
IRgbService rgbService, IRgbService rgbService,
IMessageService messageService, IMessageService messageService,
IModelValidator<SurfaceDeviceConfigViewModel> validator) : base(validator) IModelValidator<DevicePropertiesTabViewModel> validator) : base(validator)
{ {
_coreService = coreService; _coreService = coreService;
_rgbService = rgbService; _rgbService = rgbService;
_messageService = messageService; _messageService = messageService;
Device = device; Device = device;
DisplayName = "PROPERTIES";
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;
} }
public ArtemisDevice Device { get; } 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 public int X
{ {
get => _x; get => _x;
@ -79,7 +57,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
set => SetAndNotify(ref _y, value); set => SetAndNotify(ref _y, value);
} }
public float Scale public double Scale
{ {
get => _scale; get => _scale;
set => SetAndNotify(ref _scale, value); set => SetAndNotify(ref _scale, value);
@ -91,19 +69,19 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
set => SetAndNotify(ref _rotation, value); set => SetAndNotify(ref _rotation, value);
} }
public float RedScale public double RedScale
{ {
get => _redScale; get => _redScale;
set => SetAndNotify(ref _redScale, value); set => SetAndNotify(ref _redScale, value);
} }
public float GreenScale public double GreenScale
{ {
get => _greenScale; get => _greenScale;
set => SetAndNotify(ref _greenScale, value); set => SetAndNotify(ref _greenScale, value);
} }
public float BlueScale public double BlueScale
{ {
get => _blueScale; get => _blueScale;
set => SetAndNotify(ref _blueScale, value); set => SetAndNotify(ref _blueScale, value);
@ -121,32 +99,11 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
set => SetAndNotify(ref _displayOnDevices, value); 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() public void ApplyScaling()
{ {
Device.RedScale = RedScale / 100f; Device.RedScale = RedScale / 100d;
Device.GreenScale = GreenScale / 100f; Device.GreenScale = GreenScale / 100d;
Device.BlueScale = BlueScale / 100f; Device.BlueScale = BlueScale / 100d;
} }
public void BrowseCustomLayout(object sender, MouseEventArgs e) 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.RedScale = _initialRedScale;
Device.GreenScale = _initialGreenScale; Device.GreenScale = _initialGreenScale;
Device.BlueScale = _initialBlueScale; 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) private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == nameof(Device.CustomLayoutPath)) if (e.PropertyName == nameof(Device.CustomLayoutPath)) _rgbService.ApplyBestDeviceLayout(Device);
{
_rgbService.ApplyBestDeviceLayout(Device);
}
} }
private void OnFrameRendering(object sender, FrameRenderingEventArgs e) 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); e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
} }
#endregion
} }
} }

View File

@ -1,10 +1,10 @@
using FluentValidation; using FluentValidation;
namespace Artemis.UI.Screens.SurfaceEditor.Dialogs namespace Artemis.UI.Screens.Settings.Device.Tabs
{ {
public class SurfaceDeviceConfigViewModelValidator : AbstractValidator<SurfaceDeviceConfigViewModel> public class DevicePropertiesTabViewModelValidator : AbstractValidator<DevicePropertiesTabViewModel>
{ {
public SurfaceDeviceConfigViewModelValidator() public DevicePropertiesTabViewModelValidator()
{ {
RuleFor(m => m.X).GreaterThanOrEqualTo(0).WithMessage("X-coordinate must be 0 or greater"); RuleFor(m => m.X).GreaterThanOrEqualTo(0).WithMessage("X-coordinate must be 0 or greater");

View File

@ -54,12 +54,6 @@
</CheckBox> </CheckBox>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}" Padding="2 0 2 0"> <materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}" Padding="2 0 2 0">
<StackPanel> <StackPanel>
<Button Command="{s:Action ShowDeviceDebugger}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Tools" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Show device debugger</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action OpenPluginDirectory}"> <Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" /> <materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" />

View File

@ -60,11 +60,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
_deviceService.IdentifyDevice(Device); _deviceService.IdentifyDevice(Device);
} }
public void ShowDeviceDebugger()
{
_windowManager.ShowWindow(_deviceDebugVmFactory.Create(Device));
}
public void OpenPluginDirectory() public void OpenPluginDirectory()
{ {
try try
@ -87,16 +82,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
_rgbService.SaveDevice(Device); _rgbService.SaveDevice(Device);
} }
public async Task ViewProperties() public void ViewProperties()
{ {
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceConfigViewModel>( _windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(Device));
new Dictionary<string, object> {{"device", Device}}
);
if ((bool) madeChanges)
_rgbService.SaveDevice(Device);
} }
private async Task UpdateIsDeviceEnabled(bool value) private async Task UpdateIsDeviceEnabled(bool value)
{ {
if (!value) if (!value)

View File

@ -59,12 +59,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{ {
Items.Clear(); Items.Clear();
await Task.Delay(200); await Task.Delay(200);
_instances = _pluginManagementService.GetAllPlugins() GetPluginInstances();
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
UpdatePluginSearch();
}); });
base.OnActivate(); base.OnActivate();
@ -80,14 +75,21 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{ {
Plugin plugin = _pluginManagementService.ImportPlugin(dialog.FileName); Plugin plugin = _pluginManagementService.ImportPlugin(dialog.FileName);
_instances = _pluginManagementService.GetAllPlugins() GetPluginInstances();
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
SearchPluginInput = plugin.Info.Name; SearchPluginInput = plugin.Info.Name;
_messageService.ShowMessage($"Imported plugin: {plugin.Info.Name}"); _messageService.ShowMessage($"Imported plugin: {plugin.Info.Name}");
} }
} }
public void GetPluginInstances()
{
_instances = _pluginManagementService.GetAllPlugins()
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
UpdatePluginSearch();
}
} }
} }

View File

@ -53,15 +53,28 @@
</Grid> </Grid>
<Button Grid.Row="1" <StackPanel Grid.Row="1"
Grid.Column="0" Grid.Column="0"
VerticalAlignment="Bottom"
Orientation="Horizontal">
<Button
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Style="{StaticResource MaterialDesignOutlinedButton}" Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="Open the plugins settings window" ToolTip="Open the plugins settings window"
Margin="4" Margin="4"
Command="{s:Action OpenSettings}"> Command="{s:Action OpenSettings}">
SETTINGS SETTINGS
</Button> </Button>
<Button
VerticalAlignment="Bottom"
Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="Remove plugin"
Margin="4"
Command="{s:Action Remove}">
<materialDesign:PackIcon Kind="DeleteForever" />
</Button>
</StackPanel>
<CheckBox Grid.Row="1" <CheckBox Grid.Row="1"
Grid.Column="1" Grid.Column="1"
@ -70,7 +83,13 @@
Margin="8" Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}"> Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}">
Plugin enabled <StackPanel Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock>
<materialDesign:PackIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip="Plugin requires admin rights"
Visibility="{Binding Plugin.Info.RequiresAdmin, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"/>
</StackPanel>
</CheckBox> </CheckBox>
<ProgressBar Grid.Row="1" <ProgressBar Grid.Row="1"

View File

@ -19,6 +19,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory; private readonly ISettingsVmFactory _settingsVmFactory;
private readonly ICoreService _coreService;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
private bool _enabling; private bool _enabling;
@ -26,6 +27,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public PluginSettingsViewModel(Plugin plugin, public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory, ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowManager windowManager, IWindowManager windowManager,
IDialogService dialogService, IDialogService dialogService,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
@ -34,6 +36,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
Plugin = plugin; Plugin = plugin;
_settingsVmFactory = settingsVmFactory; _settingsVmFactory = settingsVmFactory;
_coreService = coreService;
_windowManager = windowManager; _windowManager = windowManager;
_dialogService = dialogService; _dialogService = dialogService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
@ -82,6 +85,24 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
} }
} }
public async Task Remove()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Delete plugin", "Are you sure you want to delete this plugin?");
if (!confirmed)
return;
try
{
_pluginManagementService.RemovePlugin(Plugin);
((PluginSettingsTabViewModel) Parent).GetPluginInstances();
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
}
public void ShowLogsFolder() public void ShowLogsFolder()
{ {
try try
@ -124,6 +145,18 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{ {
Enabling = true; Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _dialogService.ShowConfirmDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
Enabling = false;
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
return;
}
}
try try
{ {
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true)); await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true));

View File

@ -1,218 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.Dialogs.SurfaceDeviceConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Dialogs"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type surfaceEditor:SurfaceDeviceConfigViewModel}}">
<UserControl.Resources>
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<StackPanel Margin="16" Width="400">
<!-- Title -->
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}" Style="{StaticResource MaterialDesignHeadline6TextBlock}" />
<!-- Body -->
<Grid Margin="0 25 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<!-- Left side -->
<StackPanel Grid.Column="0" Orientation="Vertical">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Properties
</TextBlock>
<TextBox materialDesign:HintAssist.Hint="X-coordinate"
materialDesign:TextFieldAssist.SuffixText="mm"
Text="{Binding X, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Y-coordinate"
materialDesign:TextFieldAssist.SuffixText="mm"
Text="{Binding Y, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Scale"
materialDesign:TextFieldAssist.SuffixText="times"
Text="{Binding Scale, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Rotation"
materialDesign:TextFieldAssist.SuffixText="deg"
Text="{Binding Rotation, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Margin="0 5 0 12" />
</StackPanel>
<!-- Center divider -->
<Rectangle Grid.Column="1" VerticalAlignment="Stretch" Fill="{StaticResource MaterialDesignTextBoxBorder}" Width="1" Margin="0 0 0 5" />
<!-- Right side -->
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.ColumnSpan="3" Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Color calibration
</TextBlock>
<TextBlock Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Use the sliders below to adjust the colors of your device so that it matches your other devices.
</TextBlock>
<Label Grid.Row="2"
Grid.Column="0"
Content="R"
VerticalAlignment="Center" />
<Slider Grid.Row="2"
Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding RedScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10 0"
VerticalAlignment="Center" />
<TextBox Grid.Row="2"
Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding RedScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
Width="50" />
<Label Grid.Row="3"
Grid.Column="0"
Content="G"
VerticalAlignment="Center" />
<Slider Grid.Row="3"
Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding GreenScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10 0"
VerticalAlignment="Center" />
<TextBox Grid.Row="3"
Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding GreenScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
Width="50" />
<Label Grid.Row="4"
Grid.Column="0"
Content="B"
VerticalAlignment="Center" />
<Slider Grid.Row="4"
Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding BlueScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10 0"
Ticks="100"
VerticalAlignment="Center" />
<TextBox Grid.Row="4"
Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding BlueScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
Width="50" />
<Grid Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding DisplayOnDevices}"
Content="Show preview"
VerticalAlignment="Center" />
<shared:ColorPicker Grid.Column="1"
Margin="0,0,5,0"
HorizontalAlignment="Right"
Color="{Binding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
VerticalAlignment="Center" />
</Grid>
</Grid>
</Grid>
<!-- Layout -->
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="0 25 0 0">
Custom layout
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Select a custom layout below if you want to change the appearance and/or LEDs of this device.
</TextBlock>
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding Device.CustomLayoutPath}"
VerticalAlignment="Center"
materialDesign:TextFieldAssist.HasClearButton="True"
IsReadOnly="True"
PreviewMouseLeftButtonUp="{s:Action BrowseCustomLayout}">
<materialDesign:HintAssist.Hint>
<StackPanel Orientation="Horizontal" Margin="-2 0 0 0">
<materialDesign:PackIcon Kind="Xml" Width="20" />
<TextBlock>
Layout path
</TextBlock>
</StackPanel>
</materialDesign:HintAssist.Hint>
</TextBox>
<!-- Buttons -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Style="{StaticResource MaterialDesignFlatButton}"
IsCancel="True"
Margin="0 8 8 0"
Command="{s:Action Cancel}">
<Button.CommandParameter>
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
False
</system:Boolean>
</Button.CommandParameter>
CANCEL
</Button>
<Button Style="{StaticResource MaterialDesignFlatButton}"
IsDefault="True"
Margin="0 8 8 0"
Command="{s:Action Accept}">
<Button.CommandParameter>
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
True
</system:Boolean>
</Button.CommandParameter>
APPLY
</Button>
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -201,6 +201,18 @@
</Path.Fill> </Path.Fill>
</Path> </Path>
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10" Cursor="Arrow">
<materialDesign:Card Padding="8">
<StackPanel Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding ColorDevices}"
ToolTip="If selected, each device is completely lid up with a random color">
Show random device colors
</CheckBox>
</StackPanel>
</materialDesign:Card>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0, 0, 15, 15"> <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0, 0, 15, 15">
<Button Command="{s:Action AutoArrange}" <Button Command="{s:Action AutoArrange}"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}" Style="{StaticResource MaterialDesignFloatingActionMiniButton}"

View File

@ -10,6 +10,7 @@ using System.Windows.Navigation;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Shared; using Artemis.UI.Screens.Shared;
using Artemis.UI.Screens.SurfaceEditor.Dialogs; using Artemis.UI.Screens.SurfaceEditor.Dialogs;
using Artemis.UI.Screens.SurfaceEditor.Visualization; using Artemis.UI.Screens.SurfaceEditor.Visualization;
@ -25,8 +26,9 @@ namespace Artemis.UI.Screens.SurfaceEditor
{ {
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IWindowManager _windowManager;
private readonly IDeviceDebugVmFactory _deviceDebugVmFactory;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IInputService _inputService;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private Cursor _cursor; private Cursor _cursor;
@ -39,12 +41,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
IDialogService dialogService, IDialogService dialogService,
ISettingsService settingsService, ISettingsService settingsService,
IDeviceService deviceService, IDeviceService deviceService,
IInputService inputService) IWindowManager windowManager,
IDeviceDebugVmFactory deviceDebugVmFactory)
{ {
DisplayName = "Surface Editor"; DisplayName = "Surface Editor";
SelectionRectangle = new RectangleGeometry(); SelectionRectangle = new RectangleGeometry();
PanZoomViewModel = new PanZoomViewModel(); PanZoomViewModel = new PanZoomViewModel();
Cursor = null; Cursor = null;
ColorDevices = true;
SurfaceDeviceViewModels = new BindableCollection<SurfaceDeviceViewModel>(); SurfaceDeviceViewModels = new BindableCollection<SurfaceDeviceViewModel>();
ListDeviceViewModels = new BindableCollection<ListDeviceViewModel>(); ListDeviceViewModels = new BindableCollection<ListDeviceViewModel>();
@ -54,7 +58,8 @@ namespace Artemis.UI.Screens.SurfaceEditor
_dialogService = dialogService; _dialogService = dialogService;
_settingsService = settingsService; _settingsService = settingsService;
_deviceService = deviceService; _deviceService = deviceService;
_inputService = inputService; _windowManager = windowManager;
_deviceDebugVmFactory = deviceDebugVmFactory;
} }
public BindableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; } public BindableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; }
@ -84,6 +89,12 @@ namespace Artemis.UI.Screens.SurfaceEditor
set => SetAndNotify(ref _cursor, value); set => SetAndNotify(ref _cursor, value);
} }
public bool ColorDevices
{
get => _colorDevices;
set => SetAndNotify(ref _colorDevices, value);
}
public void OpenHyperlink(object sender, RequestNavigateEventArgs e) public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
{ {
Core.Utilities.OpenUrl(e.Uri.AbsoluteUri); Core.Utilities.OpenUrl(e.Uri.AbsoluteUri);
@ -111,9 +122,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
{ {
if (!ColorDevices)
return;
foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels) foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels)
foreach (ArtemisLed artemisLed in listDeviceViewModel.Device.Leds) {
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, new SKPaint {Color = listDeviceViewModel.Color}); foreach (ArtemisLed artemisLed in listDeviceViewModel.Device.Leds)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, new SKPaint {Color = listDeviceViewModel.Color});
}
} }
#region Overrides of Screen #region Overrides of Screen
@ -122,7 +138,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
{ {
LoadWorkspaceSettings(); LoadWorkspaceSettings();
SurfaceDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => new SurfaceDeviceViewModel(d, _rgbService))); SurfaceDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => new SurfaceDeviceViewModel(d, _rgbService)));
ListDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex * -1).Select(d => new ListDeviceViewModel(d))); ListDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex * -1).Select(d => new ListDeviceViewModel(d, this)));
List<ArtemisDevice> shuffledDevices = _rgbService.EnabledDevices.OrderBy(d => Guid.NewGuid()).ToList(); List<ArtemisDevice> shuffledDevices = _rgbService.EnabledDevices.OrderBy(d => Guid.NewGuid()).ToList();
float amount = 360f / shuffledDevices.Count; float amount = 360f / shuffledDevices.Count;
@ -223,22 +239,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
_rgbService.SaveDevices(); _rgbService.SaveDevices();
} }
public async Task ViewProperties(ArtemisDevice device) public void ViewProperties(ArtemisDevice device)
{ {
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceConfigViewModel>( _windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(device));
new Dictionary<string, object> {{"device", device}}
);
if ((bool) madeChanges)
_rgbService.SaveDevice(device);
} }
public async Task DetectInput(ArtemisDevice device) public async Task DetectInput(ArtemisDevice device)
{ {
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>( object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>(new Dictionary<string, object> {{"device", device}});
new Dictionary<string, object> {{"device", device}}
);
if ((bool) madeChanges) if ((bool) madeChanges)
_rgbService.SaveDevice(device); _rgbService.SaveDevice(device);
} }
@ -249,6 +257,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
private MouseDragStatus _mouseDragStatus; private MouseDragStatus _mouseDragStatus;
private Point _mouseDragStartPoint; private Point _mouseDragStartPoint;
private bool _colorDevices;
// ReSharper disable once UnusedMember.Global - Called from view // ReSharper disable once UnusedMember.Global - Called from view
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e) public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization" xmlns:local="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:ListDeviceViewModel}}"> d:DataContext="{d:DesignInstance {x:Type local:ListDeviceViewModel}}">
@ -22,7 +23,12 @@
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Model}" /> <TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Model}" />
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" Foreground="{DynamicResource MaterialDesignBodyLight}" /> <TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" Foreground="{DynamicResource MaterialDesignBodyLight}" />
</StackPanel> </StackPanel>
<Ellipse Grid.Column="2" Width="20" Height="20" Stroke="{DynamicResource MaterialDesignBody}" StrokeThickness="1"> <Ellipse Grid.Column="2"
Width="20"
Height="20"
Stroke="{DynamicResource MaterialDesignBody}"
StrokeThickness="1"
Visibility="{Binding Parent.ColorDevices, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<Ellipse.Fill> <Ellipse.Fill>
<SolidColorBrush Color="{Binding Color, Converter={StaticResource SKColorToColorConverter}}" /> <SolidColorBrush Color="{Binding Color, Converter={StaticResource SKColorToColorConverter}}" />
</Ellipse.Fill> </Ellipse.Fill>

View File

@ -6,9 +6,17 @@ namespace Artemis.UI.Screens.SurfaceEditor.Visualization
{ {
public class ListDeviceViewModel : PropertyChangedBase public class ListDeviceViewModel : PropertyChangedBase
{ {
private bool _isSelected;
private SKColor _color; private SKColor _color;
private bool _isSelected;
public ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
{
Device = device;
Parent = surfaceEditorViewModel;
}
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public SurfaceEditorViewModel Parent { get; }
public bool IsSelected public bool IsSelected
{ {
@ -21,10 +29,5 @@ namespace Artemis.UI.Screens.SurfaceEditor.Visualization
get => _color; get => _color;
set => SetAndNotify(ref _color, value); set => SetAndNotify(ref _color, value);
} }
public ListDeviceViewModel(ArtemisDevice device)
{
Device = device;
}
} }
} }

View File

@ -1,24 +1,25 @@
<controls:MaterialWindow x:Class="Artemis.UI.Screens.TrayView" <Window x:Class="Artemis.UI.Screens.TrayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tb="http://www.hardcodet.net/taskbar" xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" mc:Ignorable="d">
mc:Ignorable="d" <tb:TaskbarIcon IconSource="/Resources/Images/Logo/logo-512.ico"
Title="Artemis"
Height="1"
Width="1"
Visibility="Hidden">
<tb:TaskbarIcon x:Name="TrayIcon"
IconSource="/Resources/Images/Logo/logo-512.ico"
MenuActivation="LeftOrRightClick" MenuActivation="LeftOrRightClick"
PopupActivation="DoubleClick" PopupActivation="DoubleClick"
ToolTipText="Artemis"
DoubleClickCommand="{s:Action TrayBringToForeground}" DoubleClickCommand="{s:Action TrayBringToForeground}"
TrayBalloonTipClicked="{s:Action OnTrayBalloonTipClicked}"> TrayBalloonTipClicked="{s:Action OnTrayBalloonTipClicked}">
<tb:TaskbarIcon.TrayToolTip>
<Border Background="{DynamicResource MaterialDesignToolTipBackground}" CornerRadius="2" Padding="5">
<TextBlock Foreground="{DynamicResource MaterialDesignPaper}">
Artemis
</TextBlock>
</Border>
</tb:TaskbarIcon.TrayToolTip>
<tb:TaskbarIcon.ContextMenu> <tb:TaskbarIcon.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="Home" Command="{s:Action TrayActivateSidebarItem}" CommandParameter="Home"> <MenuItem Header="Home" Command="{s:Action TrayActivateSidebarItem}" CommandParameter="Home">
@ -60,4 +61,4 @@
</ContextMenu> </ContextMenu>
</tb:TaskbarIcon.ContextMenu> </tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon> </tb:TaskbarIcon>
</controls:MaterialWindow> </Window>

View File

@ -4,9 +4,9 @@ using Artemis.Core.Services;
using Artemis.UI.Controllers; using Artemis.UI.Controllers;
using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Display;
using Artemis.UI.DefaultTypes.DataModel.Input; using Artemis.UI.DefaultTypes.DataModel.Input;
using Artemis.UI.DefaultTypes.PropertyInput;
using Artemis.UI.InputProviders; using Artemis.UI.InputProviders;
using Artemis.UI.Ninject; using Artemis.UI.Ninject;
using Artemis.UI.PropertyInput;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Serilog; using Serilog;

View File

@ -16,7 +16,6 @@ using Artemis.UI.Shared.Services;
using Flurl; using Flurl;
using Flurl.Http; using Flurl.Http;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
using Newtonsoft.Json.Linq;
using Serilog; using Serilog;
using File = System.IO.File; using File = System.IO.File;
@ -27,8 +26,8 @@ namespace Artemis.UI.Services
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> _autoInstallUpdates;
private readonly PluginSetting<bool> _checkForUpdates; private readonly PluginSetting<bool> _checkForUpdates;
private readonly ILogger _logger;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly ILogger _logger;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
@ -46,6 +45,32 @@ namespace Artemis.UI.Services
_checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged; _checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged;
} }
private async Task OfferUpdate(DevOpsBuild buildInfo)
{
await _dialogService.ShowDialog<UpdateDialogViewModel>(new Dictionary<string, object> {{"buildInfo", buildInfo}});
}
private async Task UpdateInstaller()
{
string downloadUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe";
string installerDirectory = Path.Combine(Constants.DataFolder, "installer");
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe");
_logger.Information("UpdateInstaller: Downloading installer from {downloadUrl}", downloadUrl);
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(downloadUrl);
if (!httpResponseMessage.IsSuccessStatusCode)
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");
_logger.Information("UpdateInstaller: Writing installer file to {installerPath}", installerPath);
if (File.Exists(installerPath))
File.Delete(installerPath);
Core.Utilities.CreateAccessibleDirectory(installerDirectory);
await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None);
await httpResponseMessage.Content.CopyToAsync(fs);
}
public async Task<bool> AutoUpdate() public async Task<bool> AutoUpdate()
{ {
if (!_checkForUpdates.Value) if (!_checkForUpdates.Value)
@ -68,7 +93,9 @@ namespace Artemis.UI.Services
return false; return false;
if (_windowService.IsMainWindowOpen) if (_windowService.IsMainWindowOpen)
{
await OfferUpdate(buildInfo); await OfferUpdate(buildInfo);
}
else if (_autoInstallUpdates.Value) else if (_autoInstallUpdates.Value)
{ {
// Lets go // Lets go
@ -92,11 +119,6 @@ namespace Artemis.UI.Services
return true; return true;
} }
private async Task OfferUpdate(DevOpsBuild buildInfo)
{
await _dialogService.ShowDialog<UpdateDialogViewModel>(new Dictionary<string, object> {{"buildInfo", buildInfo}});
}
public async Task<bool> IsUpdateAvailable() public async Task<bool> IsUpdateAvailable()
{ {
DevOpsBuild buildInfo = await GetBuildInfo(1); DevOpsBuild buildInfo = await GetBuildInfo(1);
@ -110,11 +132,13 @@ namespace Artemis.UI.Services
// Ensure the installer is up-to-date, get installer build info // Ensure the installer is up-to-date, get installer build info
DevOpsBuild buildInfo = await GetBuildInfo(6); DevOpsBuild buildInfo = await GetBuildInfo(6);
string installerPath = Path.Combine(Constants.ApplicationFolder, "Installer", "Artemis.Installer.exe"); string installerPath = Path.Combine(Constants.DataFolder, "installer", "Artemis.Installer.exe");
// Always update installer if it is missing ^^ // Always update installer if it is missing ^^
if (!File.Exists(installerPath)) if (!File.Exists(installerPath))
{
await UpdateInstaller(); await UpdateInstaller();
}
// Compare the creation date of the installer with the build date and update if needed // Compare the creation date of the installer with the build date and update if needed
else else
{ {
@ -182,27 +206,6 @@ namespace Artemis.UI.Services
.GetJsonAsync<GitHubDifference>(); .GetJsonAsync<GitHubDifference>();
} }
private async Task UpdateInstaller()
{
string downloadUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe";
string installerDirectory = Path.Combine(Constants.ApplicationFolder, "Installer");
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe");
_logger.Information("UpdateInstaller: Downloading installer from {downloadUrl}", downloadUrl);
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(downloadUrl);
if (!httpResponseMessage.IsSuccessStatusCode)
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");
_logger.Information("UpdateInstaller: Writing installer file to {installerPath}", installerPath);
if (File.Exists(installerPath))
File.Delete(installerPath);
else if (!Directory.Exists(installerDirectory))
Directory.CreateDirectory(installerDirectory);
await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None);
await httpResponseMessage.Content.CopyToAsync(fs);
}
#region Event handlers #region Event handlers
private void CheckForUpdatesOnSettingChanged(object sender, EventArgs e) private void CheckForUpdatesOnSettingChanged(object sender, EventArgs e)

View File

@ -27,9 +27,13 @@
}, },
"Hardcodet.NotifyIcon.Wpf.NetCore": { "Hardcodet.NotifyIcon.Wpf.NetCore": {
"type": "Direct", "type": "Direct",
"requested": "[1.0.14, )", "requested": "[1.0.18, )",
"resolved": "1.0.14", "resolved": "1.0.18",
"contentHash": "aNwwax4+C/xhIxTbwKHJnxh0A6hWGnqXZtppZ+/lkE8VOL+a2WuMOBgWpkiUk6Tv+pi1bc+yuHo0lh4Md6KEFw==" "contentHash": "oI8YY/gUQooA0XIIZl4TgueexJcu+MbSvCQ2+ZBZRa+rIvFCWiyk8rjgywQ17sVrhLXRn+xF8+FTrWLxetkx0A==",
"dependencies": {
"H.NotifyIcon": "1.0.18",
"System.Drawing.Common": "5.0.0"
}
}, },
"Humanizer.Core": { "Humanizer.Core": {
"type": "Direct", "type": "Direct",
@ -225,6 +229,11 @@
"resolved": "3.0.1", "resolved": "3.0.1",
"contentHash": "i7CuPSikVroBaWG8sPvO707Ex9C6BP5+r4JufKNU1FGMmiFgLJvNo1ttUg6ZiXIzUNknvIb1VUTIO9iEDucibg==" "contentHash": "i7CuPSikVroBaWG8sPvO707Ex9C6BP5+r4JufKNU1FGMmiFgLJvNo1ttUg6ZiXIzUNknvIb1VUTIO9iEDucibg=="
}, },
"H.NotifyIcon": {
"type": "Transitive",
"resolved": "1.0.18",
"contentHash": "vV0lNWD9xGeCH4pCmT8vKtax2QOmo8WwhCBgBDO3BYQtPG7Rjuf5Ua+3O8++XkZB7t9zromzxcT5nYdGDg1Puw=="
},
"HidSharp": { "HidSharp": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.1.0", "resolved": "2.1.0",