mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Merge branch 'master' into RGB.NET_update
This commit is contained in:
commit
da90c0d3a1
94 files changed
+1525
-1007
No files matched your search
@@ -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_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_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_005Cjson/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -36,9 +36,6 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public override void ApplyValue(float value)
|
||||
{
|
||||
if (ValueTypeSetExpression == null)
|
||||
return;
|
||||
|
||||
if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
|
||||
value = Math.Min(value, max);
|
||||
if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
internal BoolLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
RegisterDataBindingProperty(b => b, new GeneralDataBindingConverter<bool>());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
namespace Artemis.Core
|
||||
using System.ComponentModel;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
||||
{
|
||||
private ColorGradient? _subscribedGradient;
|
||||
|
||||
internal ColorGradientLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBindingsSupported = false;
|
||||
DataBindingsSupported = true;
|
||||
DefaultValue = new ColorGradient();
|
||||
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
private void CreateDataBindingRegistrations()
|
||||
{
|
||||
ClearDataBindingProperties();
|
||||
if (CurrentValue == null)
|
||||
return;
|
||||
|
||||
for (int index = 0; index < CurrentValue.Stops.Count; index++)
|
||||
{
|
||||
int stopIndex = index;
|
||||
|
||||
void Setter(SKColor value)
|
||||
{
|
||||
CurrentValue.Stops[stopIndex].Color = value;
|
||||
CurrentValue.OnColorValuesUpdated();
|
||||
}
|
||||
|
||||
RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
|
||||
/// </summary>
|
||||
@@ -31,6 +57,22 @@
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
|
||||
if (_subscribedGradient != BaseValue)
|
||||
{
|
||||
if (_subscribedGradient != null)
|
||||
_subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient = BaseValue;
|
||||
_subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged;
|
||||
}
|
||||
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
|
||||
private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count)
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
|
||||
#region Overrides of LayerProperty<ColorGradient>
|
||||
@@ -41,10 +83,31 @@
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
|
||||
|
||||
base.OnInitialize();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class ColorStopDataBindingConverter : DataBindingConverter<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
internal FloatLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(value => value, new FloatDataBindingConverter());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
{
|
||||
internal FloatRangeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter<FloatRange>());
|
||||
RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter<FloatRange>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter<FloatRange>(), "Start");
|
||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Artemis.Core
|
||||
{
|
||||
internal IntLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(value => value, new IntDataBindingConverter());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
{
|
||||
internal IntRangeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(range => range.Start, new IntDataBindingConverter<IntRange>());
|
||||
RegisterDataBindingProperty(range => range.End, new IntDataBindingConverter<IntRange>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter<IntRange>(), "Start");
|
||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(value => value, new SKColorDataBindingConverter());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter<SKPoint>());
|
||||
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter<SKPoint>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter<SKPoint>(), "X");
|
||||
RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter<SKPoint>(), "Y");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter<SKSize>());
|
||||
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter<SKSize>(), "Width");
|
||||
RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter<SKSize>(), "Height");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,9 +11,8 @@ namespace Artemis.Core
|
||||
public class DataModelConditionEvent : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
private IDataModelEvent? _event;
|
||||
private bool _eventTriggered;
|
||||
private bool _reinitializing;
|
||||
private DateTime _lastTrigger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
|
||||
@@ -53,22 +52,21 @@ namespace Artemis.Core
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||
|
||||
// Ensure the event has not been replaced
|
||||
if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent)
|
||||
SubscribeToDataModelEvent(dataModelEvent);
|
||||
|
||||
// Only evaluate to true once every time the event has been triggered
|
||||
if (!_eventTriggered)
|
||||
if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent)
|
||||
return false;
|
||||
// Only evaluate to true once every time the event has been triggered since the last evaluation
|
||||
if (dataModelEvent.LastTrigger <= _lastTrigger)
|
||||
return false;
|
||||
|
||||
_eventTriggered = false;
|
||||
_lastTrigger = DateTime.Now;
|
||||
|
||||
// If there is a child (root group), it must evaluate to true whenever the event triggered
|
||||
if (Children.Any())
|
||||
return Children[0].EvaluateObject(_event?.LastEventArgumentsUntyped);
|
||||
return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped);
|
||||
|
||||
// If there are no children, we always evaluate to true whenever the event triggered
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -132,7 +130,7 @@ namespace Artemis.Core
|
||||
// Target list
|
||||
EventPath?.Save();
|
||||
Entity.EventPath = EventPath?.Entity;
|
||||
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
@@ -172,16 +170,9 @@ namespace Artemis.Core
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent)
|
||||
{
|
||||
if (_event != null)
|
||||
_event.EventTriggered -= OnEventTriggered;
|
||||
|
||||
_event = dataModelEvent;
|
||||
if (_event != null)
|
||||
_event.EventTriggered += OnEventTriggered;
|
||||
if (EventPath?.GetValue() is IDataModelEvent dataModelEvent)
|
||||
_lastTrigger = dataModelEvent.LastTrigger;
|
||||
}
|
||||
|
||||
private Type? GetEventArgumentType()
|
||||
@@ -212,11 +203,6 @@ namespace Artemis.Core
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void OnEventTriggered(object? sender, EventArgs e)
|
||||
{
|
||||
_eventTriggered = true;
|
||||
}
|
||||
|
||||
private void EventPathOnPathValidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public Type? GetTargetType()
|
||||
{
|
||||
return Registration?.PropertyExpression.ReturnType;
|
||||
return Registration?.Getter.Method.ReturnType;
|
||||
}
|
||||
|
||||
private void ResetEasing(TProperty value)
|
||||
@@ -272,7 +272,7 @@ namespace Artemis.Core
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
// General
|
||||
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression);
|
||||
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
|
||||
if (registration != null)
|
||||
ApplyRegistration(registration);
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace Artemis.Core
|
||||
|
||||
// Don't save an invalid state
|
||||
if (Registration != null)
|
||||
Entity.TargetExpression = Registration.PropertyExpression.ToString();
|
||||
Entity.Identifier = Registration.DisplayName;
|
||||
|
||||
Entity.EasingTime = EasingTime;
|
||||
Entity.EasingFunction = (int) EasingFunction;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
@@ -10,21 +8,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets the data binding this converter is applied to
|
||||
/// </summary>
|
||||
@@ -40,9 +23,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public bool SupportsInterpolate { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type SupportedType => typeof(TProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
|
||||
/// </summary>
|
||||
@@ -65,12 +45,9 @@ namespace Artemis.Core
|
||||
/// <param name="value"></param>
|
||||
public virtual void ApplyValue(TProperty value)
|
||||
{
|
||||
if (DataBinding == null)
|
||||
if (DataBinding?.Registration == null)
|
||||
throw new ArtemisCoreException("Data binding converter is not yet initialized");
|
||||
if (ReferenceTypeSetExpression != null)
|
||||
ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value);
|
||||
else if (ValueTypeSetExpression != null)
|
||||
ValueTypeSetExpression(value);
|
||||
DataBinding.Registration.Setter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,9 +55,9 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public virtual TProperty GetValue()
|
||||
{
|
||||
if (DataBinding == null || GetExpression == null)
|
||||
if (DataBinding?.Registration == null)
|
||||
throw new ArtemisCoreException("Data binding converter is not yet initialized");
|
||||
return GetExpression(DataBinding.LayerProperty.CurrentValue);
|
||||
return DataBinding.Registration.Getter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,83 +81,10 @@ namespace Artemis.Core
|
||||
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
|
||||
|
||||
DataBinding = dataBinding;
|
||||
GetExpression = dataBinding.Registration.PropertyExpression.Compile();
|
||||
CreateSetExpression();
|
||||
|
||||
OnInitialized();
|
||||
}
|
||||
|
||||
private void CreateSetExpression()
|
||||
{
|
||||
// If the registration does not point towards a member of LayerProperty<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();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public Type SupportedType => typeof(TProperty);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
|
||||
namespace Artemis.Core
|
||||
@@ -9,16 +7,14 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
|
||||
{
|
||||
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty,
|
||||
DataBindingConverter<TLayerProperty, TProperty> converter,
|
||||
Expression<Func<TLayerProperty, TProperty>> propertyExpression)
|
||||
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, DataBindingConverter<TLayerProperty, TProperty> converter,
|
||||
Func<TProperty> getter, Action<TProperty> setter, string displayName)
|
||||
{
|
||||
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
|
||||
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
|
||||
PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression));
|
||||
|
||||
if (propertyExpression.Body is MemberExpression memberExpression)
|
||||
Member = memberExpression.Member;
|
||||
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
|
||||
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
|
||||
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,15 +28,17 @@ namespace Artemis.Core
|
||||
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expression that that accesses the property
|
||||
/// Gets the function to call to get the value of the property
|
||||
/// </summary>
|
||||
public Expression<Func<TLayerProperty, TProperty>> PropertyExpression { get; }
|
||||
public Func<TProperty> Getter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member the <see cref="PropertyExpression" /> targets
|
||||
/// <para><see langword="null"/> if the <see cref="PropertyExpression" /> is not a member expression</para>
|
||||
/// Gets the action to call to set the value of the property
|
||||
/// </summary>
|
||||
public MemberInfo? Member { get; }
|
||||
public Action<TProperty> Setter { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding created using this registration
|
||||
@@ -59,7 +57,7 @@ namespace Artemis.Core
|
||||
if (DataBinding != null)
|
||||
return DataBinding;
|
||||
|
||||
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString());
|
||||
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName);
|
||||
if (dataBinding == null)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
/// </summary>
|
||||
public interface IDataBindingRegistration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the display name of the data binding registration
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data binding applied using this registration
|
||||
/// </summary>
|
||||
|
||||
@@ -213,9 +213,25 @@ namespace Artemis.Core
|
||||
Expression? expression = Expression.Convert(parameter, Target.GetType());
|
||||
Expression? nullCondition = null;
|
||||
|
||||
MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!;
|
||||
foreach (DataModelPathSegment segment in _segments)
|
||||
{
|
||||
BinaryExpression notNull = Expression.NotEqual(expression, Expression.Default(expression.Type));
|
||||
BinaryExpression notNull;
|
||||
try
|
||||
{
|
||||
notNull = Expression.NotEqual(expression, Expression.Default(expression.Type));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
notNull = Expression.NotEqual(
|
||||
Expression.Call(
|
||||
null,
|
||||
equals,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))),
|
||||
Expression.Constant(true));
|
||||
}
|
||||
|
||||
nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
|
||||
expression = segment.Initialize(parameter, expression, nullCondition);
|
||||
if (expression == null)
|
||||
|
||||
@@ -97,6 +97,16 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
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>
|
||||
/// Occurs when a data binding has been enabled
|
||||
/// </summary>
|
||||
|
||||
@@ -379,21 +379,16 @@ namespace Artemis.Core
|
||||
public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null);
|
||||
|
||||
/// <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>
|
||||
/// </summary>
|
||||
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
|
||||
{
|
||||
return GetDataBindingRegistration<TProperty>(propertyExpression.ToString());
|
||||
}
|
||||
|
||||
internal DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string expression)
|
||||
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string identifier)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
|
||||
registration.PropertyExpression.ToString() == expression);
|
||||
registration.DisplayName == identifier);
|
||||
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
|
||||
/// </summary>
|
||||
/// <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>
|
||||
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)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter)
|
||||
throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'");
|
||||
|
||||
if (converter.SupportedType != propertyExpression.ReturnType)
|
||||
throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " +
|
||||
"because the provided converter does not support the property's type");
|
||||
|
||||
_dataBindingRegistrations.Add(new DataBindingRegistration<T, TProperty>(this, converter, propertyExpression));
|
||||
_dataBindingRegistrations.Clear();
|
||||
OnDataBindingPropertiesCleared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -661,6 +670,12 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
|
||||
|
||||
@@ -716,6 +731,22 @@ namespace Artemis.Core
|
||||
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>
|
||||
/// Invokes the <see cref="DataBindingEnabled" /> event
|
||||
/// </summary>
|
||||
|
||||
@@ -51,12 +51,6 @@ namespace Artemis.Core.DeviceProviders
|
||||
/// </summary>
|
||||
public bool CanDetectLogicalLayout { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Disable()
|
||||
{
|
||||
// Does not happen with device providers, they require Artemis to restart
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
|
||||
/// </summary>
|
||||
|
||||
@@ -178,12 +178,15 @@ namespace Artemis.Core
|
||||
{
|
||||
foreach (PluginFeature feature in Features)
|
||||
feature.Dispose();
|
||||
SetEnabled(false);
|
||||
|
||||
Kernel?.Dispose();
|
||||
PluginLoader?.Dispose();
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
_features.Clear();
|
||||
SetEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,10 +73,10 @@ namespace Artemis.Core
|
||||
|
||||
if (!enable)
|
||||
{
|
||||
IsEnabled = false;
|
||||
|
||||
// Even if disable failed, still leave it in a disabled state to avoid more issues
|
||||
InternalDisable();
|
||||
IsEnabled = false;
|
||||
|
||||
OnDisabled();
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +129,8 @@ namespace Artemis.Core
|
||||
|
||||
internal virtual void InternalDisable()
|
||||
{
|
||||
Disable();
|
||||
if (IsEnabled)
|
||||
Disable();
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
@@ -145,7 +146,7 @@ namespace Artemis.Core
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Disable();
|
||||
InternalDisable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ namespace Artemis.Core.Services
|
||||
/// <returns>The resulting plugin</returns>
|
||||
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>
|
||||
/// Enables the provided plugin feature
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.Ninject;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
@@ -275,6 +276,7 @@ namespace Artemis.Core.Services
|
||||
plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure =>
|
||||
{
|
||||
configure.IsUnloadable = true;
|
||||
configure.LoadInMemory = true;
|
||||
configure.PreferSharedTypes = true;
|
||||
});
|
||||
|
||||
@@ -430,7 +432,6 @@ namespace Artemis.Core.Services
|
||||
OnPluginDisabled(new PluginEventArgs(plugin));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Plugin ImportPlugin(string fileName)
|
||||
{
|
||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins"));
|
||||
@@ -449,7 +450,16 @@ namespace Artemis.Core.Services
|
||||
|
||||
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
||||
if (existing != null)
|
||||
throw new ArtemisPluginException($"A plugin with the same GUID is already loaded: {existing.Info}");
|
||||
{
|
||||
try
|
||||
{
|
||||
RemovePlugin(existing);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e);
|
||||
}
|
||||
}
|
||||
|
||||
string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", "");
|
||||
string uniqueTargetDirectory = targetDirectory;
|
||||
@@ -464,19 +474,38 @@ namespace Artemis.Core.Services
|
||||
|
||||
// Extract everything in the same archive directory to the unique plugin directory
|
||||
DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory));
|
||||
Directory.CreateDirectory(directoryInfo.FullName);
|
||||
Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
|
||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
||||
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
|
||||
{
|
||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory))
|
||||
{
|
||||
string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
||||
zipArchiveEntry.ExtractToFile(target);
|
||||
// Create folders
|
||||
if (zipArchiveEntry.FullName.EndsWith("/"))
|
||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
||||
// Extract files
|
||||
else
|
||||
zipArchiveEntry.ExtractToFile(target);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the newly extracted plugin and return the result
|
||||
return LoadPlugin(directoryInfo);
|
||||
}
|
||||
|
||||
public void RemovePlugin(Plugin plugin)
|
||||
{
|
||||
DirectoryInfo directory = plugin.Directory;
|
||||
lock (_plugins)
|
||||
{
|
||||
if (_plugins.Contains(plugin))
|
||||
UnloadPlugin(plugin);
|
||||
}
|
||||
|
||||
directory.Delete(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Features
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Artemis.Core.Services
|
||||
_modifyingProviders = true;
|
||||
|
||||
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)
|
||||
RemoveDevice(device);
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Artemis.Core.Services
|
||||
_modifyingProviders = true;
|
||||
|
||||
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)
|
||||
RemoveDevice(device);
|
||||
|
||||
|
||||
@@ -79,6 +79,11 @@ namespace Artemis.Core.Services.Models
|
||||
public void Arrange(List<ArtemisDevice> devices)
|
||||
{
|
||||
ArrangedDevices.Clear();
|
||||
|
||||
// Not much to do here
|
||||
if (!devices.Any())
|
||||
return;
|
||||
|
||||
foreach (ArtemisDevice surfaceDevice in devices)
|
||||
{
|
||||
surfaceDevice.X = 0;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -52,15 +52,65 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
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>
|
||||
/// Called whenever the end point has to process a request
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context of the request</param>
|
||||
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)
|
||||
{
|
||||
await ProcessRequest(context);
|
||||
try
|
||||
{
|
||||
OnProcessingRequest(context);
|
||||
await ProcessRequest(context);
|
||||
OnProcessedRequest(context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnRequestException(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisabled(object? sender, EventArgs e)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Core.Modules;
|
||||
using EmbedIO;
|
||||
using EmbedIO.WebApi;
|
||||
|
||||
@@ -43,6 +45,24 @@ namespace Artemis.Core.Services
|
||||
/// <returns>The resulting end point</returns>
|
||||
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>
|
||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
@@ -67,6 +64,12 @@ namespace Artemis.Core.Services
|
||||
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
|
||||
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}.");
|
||||
|
||||
// If Accept-Charset contains a wildcard, remove the header so we default to UTF8
|
||||
// This is a workaround for an EmbedIO ehh issue
|
||||
string? acceptCharset = context.Request.Headers["Accept-Charset"];
|
||||
if (acceptCharset != null && acceptCharset.Contains("*"))
|
||||
context.Request.Headers.Remove("Accept-Charset");
|
||||
|
||||
// It is up to the registration how the request is eventually handled, it might even set a response here
|
||||
await endPoint.InternalProcessRequest(context);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Core.Modules;
|
||||
using EmbedIO;
|
||||
using EmbedIO.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
@@ -28,7 +30,6 @@ namespace Artemis.Core.Services
|
||||
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
|
||||
|
||||
PluginsModule = new PluginsModule("/plugins");
|
||||
|
||||
StartWebServer();
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ namespace Artemis.Core.Services
|
||||
Server?.Dispose();
|
||||
Server = null;
|
||||
|
||||
string url = $"http://localhost:{_webServerPortSetting.Value}/";
|
||||
string url = $"http://*:{_webServerPortSetting.Value}/";
|
||||
WebApiModule apiModule = new("/api/", JsonNetSerializer);
|
||||
PluginsModule.ServerUrl = url;
|
||||
WebServer server = new WebServer(o => o.WithUrlPrefix(url).WithMode(HttpListenerMode.EmbedIO))
|
||||
@@ -126,6 +127,29 @@ namespace Artemis.Core.Services
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public DataModelJsonPluginEndPoint<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)
|
||||
{
|
||||
PluginsModule.RemovePluginEndPoint(endPoint);
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
public class DataBindingEntity
|
||||
{
|
||||
public string TargetExpression { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public TimeSpan EasingTime { get; set; }
|
||||
public int EasingFunction { get; set; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@
|
||||
<PackageReference Include="FluentValidation" Version="9.3.0" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.0.1" />
|
||||
<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="MaterialDesignExtensions" Version="3.3.0-a01" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="3.2.0" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
||||
@@ -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.Services;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class BoolPropertyInputViewModel : PropertyInputViewModel<bool>
|
||||
{
|
||||
private List<IDataBindingRegistration> _registrations;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: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:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
|
||||
xmlns:layerBrush="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
|
||||
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}">
|
||||
|
||||
@@ -7,7 +7,7 @@ using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
|
||||
{
|
||||
|
||||
@@ -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: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:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
|
||||
@@ -3,7 +3,7 @@ using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorGradient>
|
||||
{
|
||||
|
||||
@@ -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:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
||||
@@ -4,7 +4,7 @@ using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class EnumPropertyInputViewModel<T> : PropertyInputViewModel<T> where T : Enum
|
||||
{
|
||||
|
||||
@@ -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: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:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
|
||||
@@ -4,7 +4,7 @@ using Artemis.UI.Shared.Services;
|
||||
using FluentValidation;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class FloatPropertyInputViewModel : PropertyInputViewModel<float>
|
||||
{
|
||||
@@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput
|
||||
public FloatPropertyInputViewModel(LayerProperty<float> layerProperty, IProfileEditorService profileEditorService, IModelValidator<FloatPropertyInputViewModel> validator)
|
||||
: base(layerProperty, profileEditorService, validator)
|
||||
{
|
||||
_registration = layerProperty.GetDataBindingRegistration(value => value);
|
||||
_registration = layerProperty.GetDataBindingRegistration<float>("Value");
|
||||
}
|
||||
|
||||
public bool IsEnabled => _registration.DataBinding == null;
|
||||
|
||||
@@ -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: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.PropertyInput"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
|
||||
@@ -5,7 +5,7 @@ using Artemis.UI.Shared.Services;
|
||||
using FluentValidation;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class FloatRangePropertyInputViewModel : PropertyInputViewModel<FloatRange>
|
||||
{
|
||||
@@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput
|
||||
IProfileEditorService profileEditorService,
|
||||
IModelValidator<FloatRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
|
||||
{
|
||||
_startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start);
|
||||
_endRegistration = layerProperty.GetDataBindingRegistration(range => range.End);
|
||||
_startRegistration = layerProperty.GetDataBindingRegistration<float>("Start");
|
||||
_endRegistration = layerProperty.GetDataBindingRegistration<float>("End");
|
||||
}
|
||||
|
||||
public float Start
|
||||
|
||||
@@ -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: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:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
|
||||
@@ -4,7 +4,7 @@ using Artemis.UI.Shared.Services;
|
||||
using FluentValidation;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class IntPropertyInputViewModel : PropertyInputViewModel<int>
|
||||
{
|
||||
@@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput
|
||||
public IntPropertyInputViewModel(LayerProperty<int> layerProperty, IProfileEditorService profileEditorService, IModelValidator<IntPropertyInputViewModel> validator)
|
||||
: base(layerProperty, profileEditorService, validator)
|
||||
{
|
||||
_registration = layerProperty.GetDataBindingRegistration(value => value);
|
||||
_registration = layerProperty.GetDataBindingRegistration<int>("Value");
|
||||
}
|
||||
|
||||
public bool IsEnabled => _registration.DataBinding == null;
|
||||
|
||||
@@ -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: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.PropertyInput"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
|
||||
@@ -5,7 +5,7 @@ using Artemis.UI.Shared.Services;
|
||||
using FluentValidation;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class IntRangePropertyInputViewModel : PropertyInputViewModel<IntRange>
|
||||
{
|
||||
@@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput
|
||||
IProfileEditorService profileEditorService,
|
||||
IModelValidator<IntRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
|
||||
{
|
||||
_startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start);
|
||||
_endRegistration = layerProperty.GetDataBindingRegistration(range => range.End);
|
||||
_startRegistration = layerProperty.GetDataBindingRegistration<int>("Start");
|
||||
_endRegistration = layerProperty.GetDataBindingRegistration<int>("End");
|
||||
}
|
||||
|
||||
public int Start
|
||||
|
||||
@@ -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: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:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25" d:DesignWidth="800"
|
||||
|
||||
@@ -3,7 +3,7 @@ using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class SKColorPropertyInputViewModel : PropertyInputViewModel<SKColor>
|
||||
{
|
||||
@@ -11,7 +11,7 @@ namespace Artemis.UI.PropertyInput
|
||||
|
||||
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;
|
||||
|
||||
@@ -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: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:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance propertyInput:SKPointPropertyInputViewModel}">
|
||||
|
||||
@@ -6,7 +6,7 @@ using FluentValidation;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class SKPointPropertyInputViewModel : PropertyInputViewModel<SKPoint>
|
||||
{
|
||||
@@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput
|
||||
public SKPointPropertyInputViewModel(LayerProperty<SKPoint> layerProperty, IProfileEditorService profileEditorService,
|
||||
IModelValidator<SKPointPropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
|
||||
{
|
||||
_xRegistration = layerProperty.GetDataBindingRegistration(point => point.X);
|
||||
_yRegistration = layerProperty.GetDataBindingRegistration(point => point.Y);
|
||||
_xRegistration = layerProperty.GetDataBindingRegistration<float>("X");
|
||||
_yRegistration = layerProperty.GetDataBindingRegistration<float>("Y");
|
||||
}
|
||||
|
||||
public float X
|
||||
|
||||
@@ -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: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.PropertyInput"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
|
||||
@@ -8,7 +8,7 @@ using Stylet;
|
||||
|
||||
// using PropertyChanged;
|
||||
|
||||
namespace Artemis.UI.PropertyInput
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
{
|
||||
public class SKSizePropertyInputViewModel : PropertyInputViewModel<SKSize>
|
||||
{
|
||||
@@ -18,8 +18,8 @@ namespace Artemis.UI.PropertyInput
|
||||
public SKSizePropertyInputViewModel(LayerProperty<SKSize> layerProperty, IProfileEditorService profileEditorService,
|
||||
IModelValidator<SKSizePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
|
||||
{
|
||||
_widthRegistration = layerProperty.GetDataBindingRegistration(size => size.Width);
|
||||
_heightRegistration = layerProperty.GetDataBindingRegistration(size => size.Height);
|
||||
_widthRegistration = layerProperty.GetDataBindingRegistration<float>("Width");
|
||||
_heightRegistration = layerProperty.GetDataBindingRegistration<float>("Height");
|
||||
}
|
||||
|
||||
// Since SKSize is immutable we need to create properties that replace the SKSize entirely
|
||||
|
||||
@@ -15,6 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
|
||||
using Artemis.UI.Screens.ProfileEditor.Visualization;
|
||||
using Artemis.UI.Screens.ProfileEditor.Visualization.Tools;
|
||||
using Artemis.UI.Screens.Settings.Debug;
|
||||
using Artemis.UI.Screens.Settings.Device;
|
||||
using Artemis.UI.Screens.Settings.Device.Tabs;
|
||||
using Artemis.UI.Screens.Settings.Tabs.Devices;
|
||||
using Artemis.UI.Screens.Settings.Tabs.Plugins;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
@@ -43,7 +45,10 @@ namespace Artemis.UI.Ninject.Factories
|
||||
|
||||
public interface IDeviceDebugVmFactory : IVmFactory
|
||||
{
|
||||
DeviceDebugViewModel Create(ArtemisDevice device);
|
||||
DeviceDialogViewModel DeviceDialogViewModel(ArtemisDevice device);
|
||||
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device);
|
||||
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
|
||||
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device);
|
||||
}
|
||||
|
||||
public interface IProfileTreeVmFactory : IVmFactory
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<RadioButton Grid.Column="0"
|
||||
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">
|
||||
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
RESTART
|
||||
@@ -215,7 +215,7 @@
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="1"
|
||||
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">
|
||||
<materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
IGNORE
|
||||
@@ -230,7 +230,7 @@
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="2"
|
||||
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">
|
||||
<materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
COPY
|
||||
|
||||
+23
-1
@@ -38,7 +38,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
public RenderProfileElement RenderProfileElement
|
||||
{
|
||||
get => _renderProfileElement;
|
||||
set => SetAndNotify(ref _renderProfileElement, value);
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _renderProfileElement, value)) return;
|
||||
NotifyOfPropertyChange(nameof(DisplayContinuously));
|
||||
NotifyOfPropertyChange(nameof(AlwaysFinishTimeline));
|
||||
NotifyOfPropertyChange(nameof(EventOverlapMode));
|
||||
}
|
||||
}
|
||||
|
||||
public bool DisplayContinuously
|
||||
@@ -65,6 +71,17 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
}
|
||||
}
|
||||
|
||||
public TimeLineEventOverlapMode EventOverlapMode
|
||||
{
|
||||
get => RenderProfileElement?.Timeline.EventOverlapMode ?? TimeLineEventOverlapMode.Restart;
|
||||
set
|
||||
{
|
||||
if (RenderProfileElement == null || RenderProfileElement?.Timeline.EventOverlapMode == value) return;
|
||||
RenderProfileElement.Timeline.EventOverlapMode = value;
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ConditionBehaviourEnabled => RenderProfileElement != null;
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
@@ -119,5 +136,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
|
||||
IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
|
||||
}
|
||||
|
||||
public void EventTriggerModeSelected()
|
||||
{
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -38,11 +38,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
||||
_profileEditorService = profileEditorService;
|
||||
_dataBindingsVmFactory = dataBindingsVmFactory;
|
||||
|
||||
if (Registration.Member != null)
|
||||
DisplayName = Registration.Member.Name.ToUpper();
|
||||
else
|
||||
DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper();
|
||||
|
||||
DisplayName = Registration.DisplayName.ToUpper();
|
||||
AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true);
|
||||
DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType)));
|
||||
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
|
||||
@@ -106,8 +102,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
base.OnInitialActivate();
|
||||
Initialize();
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
|
||||
+8
-1
@@ -6,11 +6,18 @@
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
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>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}" TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="-12 0" Text="{Binding DisplayName}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
+49
-5
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared.Services;
|
||||
@@ -11,7 +14,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
||||
{
|
||||
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private ILayerProperty? _selectedDataBinding;
|
||||
private int _selectedItemIndex;
|
||||
private bool _updating;
|
||||
|
||||
public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory)
|
||||
{
|
||||
@@ -24,9 +29,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
||||
get => _selectedItemIndex;
|
||||
set => SetAndNotify(ref _selectedItemIndex, value);
|
||||
}
|
||||
|
||||
|
||||
private void CreateDataBindingViewModels()
|
||||
{
|
||||
int oldIndex = SelectedItemIndex;
|
||||
Items.Clear();
|
||||
|
||||
ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding;
|
||||
@@ -37,26 +43,64 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
||||
|
||||
// Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving
|
||||
// and creating the actual data bindings
|
||||
foreach (IDataBindingRegistration registration in registrations)
|
||||
Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration));
|
||||
Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration)));
|
||||
|
||||
SelectedItemIndex = 0;
|
||||
SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex;
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e)
|
||||
{
|
||||
CreateDataBindingViewModels();
|
||||
SubscribeToSelectedDataBinding();
|
||||
|
||||
SelectedItemIndex = 0;
|
||||
}
|
||||
|
||||
private void SubscribeToSelectedDataBinding()
|
||||
{
|
||||
if (_selectedDataBinding != null)
|
||||
{
|
||||
_selectedDataBinding.DataBindingPropertyRegistered -= DataBindingRegistrationsChanged;
|
||||
_selectedDataBinding.DataBindingPropertiesCleared -= DataBindingRegistrationsChanged;
|
||||
}
|
||||
|
||||
_selectedDataBinding = _profileEditorService.SelectedDataBinding;
|
||||
if (_selectedDataBinding != null)
|
||||
{
|
||||
_selectedDataBinding.DataBindingPropertyRegistered += DataBindingRegistrationsChanged;
|
||||
_selectedDataBinding.DataBindingPropertiesCleared += DataBindingRegistrationsChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void DataBindingRegistrationsChanged(object sender, LayerPropertyEventArgs e)
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
|
||||
_updating = true;
|
||||
Execute.PostToUIThread(async () =>
|
||||
{
|
||||
await Task.Delay(200);
|
||||
CreateDataBindingViewModels();
|
||||
_updating = false;
|
||||
});
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
_profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
|
||||
CreateDataBindingViewModels();
|
||||
SubscribeToSelectedDataBinding();
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
SelectedItemIndex = 0;
|
||||
base.OnActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
|
||||
@@ -302,7 +302,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
|
||||
TimeSpan delta = DateTime.Now - _lastUpdate;
|
||||
_lastUpdate = DateTime.Now;
|
||||
|
||||
if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null || _profileEditorService.Playing)
|
||||
if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null)
|
||||
return;
|
||||
|
||||
foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders()
|
||||
|
||||
@@ -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>
|
||||
@@ -6,10 +6,10 @@
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core"
|
||||
xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
|
||||
<UserControl.Resources>
|
||||
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
@@ -19,6 +19,10 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Margin="0 0 0 5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
@@ -29,16 +33,18 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Property searching (nyi) -->
|
||||
<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 -->
|
||||
<TextBlock Grid.Column="3" VerticalAlignment="Center">Filter module</TextBlock>
|
||||
<ToggleButton Grid.Column="4"
|
||||
<TextBlock Grid.Row="0" Grid.Column="3" VerticalAlignment="Center">Filter module</TextBlock>
|
||||
<ToggleButton Grid.Row="0"
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5 0"
|
||||
Style="{StaticResource MaterialDesignSwitchToggleButton}"
|
||||
IsChecked="{Binding IsModuleFilterEnabled}" />
|
||||
<ComboBox Grid.Column="5"
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.Column="5"
|
||||
VerticalAlignment="Center"
|
||||
wpf:HintAssist.Hint="Select a module"
|
||||
IsEditable="True"
|
||||
@@ -48,6 +54,17 @@
|
||||
IsEnabled="{Binding IsModuleFilterEnabled}"
|
||||
SelectedItem="{Binding SelectedModule}"
|
||||
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>
|
||||
<TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch">
|
||||
<TreeView.Resources>
|
||||
|
||||
@@ -20,12 +20,13 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
||||
private DataModelPropertiesViewModel _mainDataModel;
|
||||
private string _propertySearch;
|
||||
private Module _selectedModule;
|
||||
private bool _slowUpdates;
|
||||
|
||||
public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_dataModelUIService = dataModelUIService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_updateTimer = new Timer(500);
|
||||
_updateTimer = new Timer(25);
|
||||
|
||||
DisplayName = "Data model";
|
||||
Modules = new BindableCollection<Module>();
|
||||
@@ -43,6 +44,16 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
||||
set => SetAndNotify(ref _propertySearch, value);
|
||||
}
|
||||
|
||||
public bool SlowUpdates
|
||||
{
|
||||
get => _slowUpdates;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _slowUpdates, value);
|
||||
_updateTimer.Interval = _slowUpdates ? 500 : 25;
|
||||
}
|
||||
}
|
||||
|
||||
public BindableCollection<Module> Modules { get; }
|
||||
|
||||
public Module SelectedModule
|
||||
@@ -116,7 +127,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
||||
|
||||
lock (MainDataModel)
|
||||
{
|
||||
MainDataModel.Update(_dataModelUIService, null);
|
||||
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
+18
-8
@@ -2,36 +2,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using RGB.NET.Layout;
|
||||
using Stylet;
|
||||
|
||||
// using PropertyChanged;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Debug
|
||||
namespace Artemis.UI.Screens.Settings.Device
|
||||
{
|
||||
public class DeviceDebugViewModel : Screen
|
||||
public class DeviceDialogViewModel : Conductor<Screen>.Collection.OneActive
|
||||
{
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private ArtemisLed _selectedLed;
|
||||
|
||||
public DeviceDebugViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService)
|
||||
public DeviceDialogViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory)
|
||||
{
|
||||
_deviceService = deviceService;
|
||||
_rgbService = rgbService;
|
||||
_dialogService = dialogService;
|
||||
|
||||
Device = device;
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
|
||||
Items.Add(factory.DevicePropertiesTabViewModel(device));
|
||||
Items.Add(factory.DeviceInfoTabViewModel(device));
|
||||
Items.Add(factory.DeviceLedsTabViewModel(device));
|
||||
ActiveItem = Items.First();
|
||||
DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis";
|
||||
}
|
||||
|
||||
public List<ArtemisLed> SelectedLeds => SelectedLed != null ? new List<ArtemisLed> {SelectedLed} : null;
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public PanZoomViewModel PanZoomViewModel { get; }
|
||||
|
||||
public ArtemisLed SelectedLed
|
||||
{
|
||||
get => _selectedLed;
|
||||
@@ -41,6 +50,7 @@ namespace Artemis.UI.Screens.Settings.Debug
|
||||
NotifyOfPropertyChange(nameof(SelectedLeds));
|
||||
}
|
||||
}
|
||||
public List<ArtemisLed> SelectedLeds => SelectedLed != null ? new List<ArtemisLed> { SelectedLed } : null;
|
||||
|
||||
public bool CanOpenImageDirectory => Device.Layout?.Image != null;
|
||||
|
||||
@@ -135,7 +145,7 @@ namespace Artemis.UI.Screens.Settings.Debug
|
||||
|
||||
foreach (ArtemisLedLayout ledLayout in Device.Layout.Leds)
|
||||
{
|
||||
if (ledLayout.LayoutCustomLedData.LogicalLayouts == null)
|
||||
if (ledLayout.LayoutCustomLedData.LogicalLayouts == null)
|
||||
continue;
|
||||
|
||||
// Only the image of the current logical layout is available as an URI, iterate each layout and find the images manually
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
+69
-73
@@ -5,68 +5,46 @@ using System.Windows.Input;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
{
|
||||
public class SurfaceDeviceConfigViewModel : DialogViewModelBase
|
||||
public class DevicePropertiesTabViewModel : Screen
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly float _initialRedScale;
|
||||
private readonly float _initialGreenScale;
|
||||
private readonly float _initialBlueScale;
|
||||
private int _rotation;
|
||||
private float _scale;
|
||||
private int _x;
|
||||
private int _y;
|
||||
private float _redScale;
|
||||
private float _greenScale;
|
||||
private float _blueScale;
|
||||
private readonly IRgbService _rgbService;
|
||||
private double _blueScale;
|
||||
private SKColor _currentColor;
|
||||
private bool _displayOnDevices;
|
||||
private double _greenScale;
|
||||
private double _initialBlueScale;
|
||||
private double _initialGreenScale;
|
||||
private double _initialRedScale;
|
||||
private double _redScale;
|
||||
private int _rotation;
|
||||
private double _scale;
|
||||
private int _x;
|
||||
private int _y;
|
||||
|
||||
public SurfaceDeviceConfigViewModel(ArtemisDevice device,
|
||||
public DevicePropertiesTabViewModel(ArtemisDevice device,
|
||||
ICoreService coreService,
|
||||
IRgbService rgbService,
|
||||
IMessageService messageService,
|
||||
IModelValidator<SurfaceDeviceConfigViewModel> validator) : base(validator)
|
||||
IModelValidator<DevicePropertiesTabViewModel> validator) : base(validator)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_rgbService = rgbService;
|
||||
_messageService = messageService;
|
||||
|
||||
Device = device;
|
||||
|
||||
X = (int) Device.X;
|
||||
Y = (int) Device.Y;
|
||||
Scale = Device.Scale;
|
||||
Rotation = (int) Device.Rotation;
|
||||
RedScale = Device.RedScale * 100f;
|
||||
GreenScale = Device.GreenScale * 100f;
|
||||
BlueScale = Device.BlueScale * 100f;
|
||||
//we need to store the initial values to be able to restore them when the user clicks "Cancel"
|
||||
_initialRedScale = Device.RedScale;
|
||||
_initialGreenScale = Device.GreenScale;
|
||||
_initialBlueScale = Device.BlueScale;
|
||||
CurrentColor = SKColors.White;
|
||||
_coreService.FrameRendering += OnFrameRendering;
|
||||
Device.PropertyChanged += DeviceOnPropertyChanged;
|
||||
DisplayName = "PROPERTIES";
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
|
||||
{
|
||||
_coreService.FrameRendering -= OnFrameRendering;
|
||||
Device.PropertyChanged -= DeviceOnPropertyChanged;
|
||||
base.OnDialogClosed(sender, e);
|
||||
}
|
||||
|
||||
public int X
|
||||
{
|
||||
get => _x;
|
||||
@@ -79,7 +57,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
set => SetAndNotify(ref _y, value);
|
||||
}
|
||||
|
||||
public float Scale
|
||||
public double Scale
|
||||
{
|
||||
get => _scale;
|
||||
set => SetAndNotify(ref _scale, value);
|
||||
@@ -91,19 +69,19 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
set => SetAndNotify(ref _rotation, value);
|
||||
}
|
||||
|
||||
public float RedScale
|
||||
public double RedScale
|
||||
{
|
||||
get => _redScale;
|
||||
set => SetAndNotify(ref _redScale, value);
|
||||
}
|
||||
|
||||
public float GreenScale
|
||||
public double GreenScale
|
||||
{
|
||||
get => _greenScale;
|
||||
set => SetAndNotify(ref _greenScale, value);
|
||||
}
|
||||
|
||||
public float BlueScale
|
||||
public double BlueScale
|
||||
{
|
||||
get => _blueScale;
|
||||
set => SetAndNotify(ref _blueScale, value);
|
||||
@@ -121,32 +99,11 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
set => SetAndNotify(ref _displayOnDevices, value);
|
||||
}
|
||||
|
||||
public async Task Accept()
|
||||
{
|
||||
await ValidateAsync();
|
||||
if (HasErrors)
|
||||
return;
|
||||
|
||||
_coreService.ModuleRenderingDisabled = true;
|
||||
await Task.Delay(100);
|
||||
|
||||
Device.X = X;
|
||||
Device.Y = Y;
|
||||
Device.Scale = Scale;
|
||||
Device.Rotation = Rotation;
|
||||
Device.RedScale = RedScale / 100f;
|
||||
Device.GreenScale = GreenScale / 100f;
|
||||
Device.BlueScale = BlueScale / 100f;
|
||||
|
||||
_coreService.ModuleRenderingDisabled = false;
|
||||
Session.Close(true);
|
||||
}
|
||||
|
||||
public void ApplyScaling()
|
||||
{
|
||||
Device.RedScale = RedScale / 100f;
|
||||
Device.GreenScale = GreenScale / 100f;
|
||||
Device.BlueScale = BlueScale / 100f;
|
||||
Device.RedScale = RedScale / 100d;
|
||||
Device.GreenScale = GreenScale / 100d;
|
||||
Device.BlueScale = BlueScale / 100d;
|
||||
}
|
||||
|
||||
public void BrowseCustomLayout(object sender, MouseEventArgs e)
|
||||
@@ -169,21 +126,58 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
public async Task Apply()
|
||||
{
|
||||
await ValidateAsync();
|
||||
if (HasErrors)
|
||||
return;
|
||||
|
||||
_coreService.ModuleRenderingDisabled = true;
|
||||
await Task.Delay(100);
|
||||
|
||||
Device.X = X;
|
||||
Device.Y = Y;
|
||||
Device.Scale = Scale;
|
||||
Device.Rotation = Rotation;
|
||||
Device.RedScale = RedScale / 100d;
|
||||
Device.GreenScale = GreenScale / 100d;
|
||||
Device.BlueScale = BlueScale / 100d;
|
||||
|
||||
_coreService.ModuleRenderingDisabled = false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Device.RedScale = _initialRedScale;
|
||||
Device.GreenScale = _initialGreenScale;
|
||||
Device.BlueScale = _initialBlueScale;
|
||||
|
||||
base.Cancel();
|
||||
}
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
X = (int) Device.X;
|
||||
Y = (int) Device.Y;
|
||||
Scale = Device.Scale;
|
||||
Rotation = (int) Device.Rotation;
|
||||
RedScale = Device.RedScale * 100d;
|
||||
GreenScale = Device.GreenScale * 100d;
|
||||
BlueScale = Device.BlueScale * 100d;
|
||||
//we need to store the initial values to be able to restore them when the user clicks "Cancel"
|
||||
_initialRedScale = Device.RedScale;
|
||||
_initialGreenScale = Device.GreenScale;
|
||||
_initialBlueScale = Device.BlueScale;
|
||||
CurrentColor = SKColors.White;
|
||||
_coreService.FrameRendering += OnFrameRendering;
|
||||
Device.PropertyChanged += DeviceOnPropertyChanged;
|
||||
|
||||
base.OnActivate();
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Device.CustomLayoutPath))
|
||||
{
|
||||
_rgbService.ApplyBestDeviceLayout(Device);
|
||||
}
|
||||
if (e.PropertyName == nameof(Device.CustomLayoutPath)) _rgbService.ApplyBestDeviceLayout(Device);
|
||||
}
|
||||
|
||||
private void OnFrameRendering(object sender, FrameRenderingEventArgs e)
|
||||
@@ -197,5 +191,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
};
|
||||
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
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");
|
||||
|
||||
@@ -54,12 +54,6 @@
|
||||
</CheckBox>
|
||||
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}" Padding="2 0 2 0">
|
||||
<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}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" />
|
||||
|
||||
@@ -60,11 +60,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
|
||||
_deviceService.IdentifyDevice(Device);
|
||||
}
|
||||
|
||||
public void ShowDeviceDebugger()
|
||||
{
|
||||
_windowManager.ShowWindow(_deviceDebugVmFactory.Create(Device));
|
||||
}
|
||||
|
||||
public void OpenPluginDirectory()
|
||||
{
|
||||
try
|
||||
@@ -87,16 +82,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
|
||||
_rgbService.SaveDevice(Device);
|
||||
}
|
||||
|
||||
public async Task ViewProperties()
|
||||
public void ViewProperties()
|
||||
{
|
||||
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceConfigViewModel>(
|
||||
new Dictionary<string, object> {{"device", Device}}
|
||||
);
|
||||
|
||||
if ((bool) madeChanges)
|
||||
_rgbService.SaveDevice(Device);
|
||||
_windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(Device));
|
||||
}
|
||||
|
||||
private async Task UpdateIsDeviceEnabled(bool value)
|
||||
{
|
||||
if (!value)
|
||||
|
||||
@@ -59,12 +59,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
{
|
||||
Items.Clear();
|
||||
await Task.Delay(200);
|
||||
_instances = _pluginManagementService.GetAllPlugins()
|
||||
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
|
||||
.OrderBy(i => i.Plugin.Info.Name)
|
||||
.ToList();
|
||||
|
||||
UpdatePluginSearch();
|
||||
GetPluginInstances();
|
||||
});
|
||||
|
||||
base.OnActivate();
|
||||
@@ -80,14 +75,21 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
{
|
||||
Plugin plugin = _pluginManagementService.ImportPlugin(dialog.FileName);
|
||||
|
||||
_instances = _pluginManagementService.GetAllPlugins()
|
||||
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
|
||||
.OrderBy(i => i.Plugin.Info.Name)
|
||||
.ToList();
|
||||
GetPluginInstances();
|
||||
SearchPluginInput = 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,15 +53,28 @@
|
||||
</Grid>
|
||||
|
||||
|
||||
<Button Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
<StackPanel Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
VerticalAlignment="Bottom"
|
||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
||||
ToolTip="Open the plugins settings window"
|
||||
Margin="4"
|
||||
Command="{s:Action OpenSettings}">
|
||||
SETTINGS
|
||||
</Button>
|
||||
SETTINGS
|
||||
</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"
|
||||
Grid.Column="1"
|
||||
@@ -70,7 +83,13 @@
|
||||
Margin="8"
|
||||
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
|
||||
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>
|
||||
|
||||
<ProgressBar Grid.Row="1"
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private bool _enabling;
|
||||
@@ -26,6 +27,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
|
||||
public PluginSettingsViewModel(Plugin plugin,
|
||||
ISettingsVmFactory settingsVmFactory,
|
||||
ICoreService coreService,
|
||||
IWindowManager windowManager,
|
||||
IDialogService dialogService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
@@ -34,6 +36,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
Plugin = plugin;
|
||||
|
||||
_settingsVmFactory = settingsVmFactory;
|
||||
_coreService = coreService;
|
||||
_windowManager = windowManager;
|
||||
_dialogService = dialogService;
|
||||
_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()
|
||||
{
|
||||
try
|
||||
@@ -124,6 +145,18 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
{
|
||||
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
|
||||
{
|
||||
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true));
|
||||
|
||||
@@ -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>
|
||||
@@ -201,6 +201,18 @@
|
||||
</Path.Fill>
|
||||
</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">
|
||||
<Button Command="{s:Action AutoArrange}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Windows.Navigation;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
using Artemis.UI.Screens.SurfaceEditor.Dialogs;
|
||||
using Artemis.UI.Screens.SurfaceEditor.Visualization;
|
||||
@@ -25,8 +26,9 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private readonly IDeviceDebugVmFactory _deviceDebugVmFactory;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IInputService _inputService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private Cursor _cursor;
|
||||
@@ -39,12 +41,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
IDialogService dialogService,
|
||||
ISettingsService settingsService,
|
||||
IDeviceService deviceService,
|
||||
IInputService inputService)
|
||||
IWindowManager windowManager,
|
||||
IDeviceDebugVmFactory deviceDebugVmFactory)
|
||||
{
|
||||
DisplayName = "Surface Editor";
|
||||
SelectionRectangle = new RectangleGeometry();
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
Cursor = null;
|
||||
ColorDevices = true;
|
||||
|
||||
SurfaceDeviceViewModels = new BindableCollection<SurfaceDeviceViewModel>();
|
||||
ListDeviceViewModels = new BindableCollection<ListDeviceViewModel>();
|
||||
@@ -54,7 +58,8 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
_dialogService = dialogService;
|
||||
_settingsService = settingsService;
|
||||
_deviceService = deviceService;
|
||||
_inputService = inputService;
|
||||
_windowManager = windowManager;
|
||||
_deviceDebugVmFactory = deviceDebugVmFactory;
|
||||
}
|
||||
|
||||
public BindableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; }
|
||||
@@ -84,6 +89,12 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
set => SetAndNotify(ref _cursor, value);
|
||||
}
|
||||
|
||||
public bool ColorDevices
|
||||
{
|
||||
get => _colorDevices;
|
||||
set => SetAndNotify(ref _colorDevices, value);
|
||||
}
|
||||
|
||||
public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Core.Utilities.OpenUrl(e.Uri.AbsoluteUri);
|
||||
@@ -111,9 +122,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
|
||||
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
|
||||
{
|
||||
if (!ColorDevices)
|
||||
return;
|
||||
|
||||
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
|
||||
@@ -122,7 +138,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
LoadWorkspaceSettings();
|
||||
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();
|
||||
float amount = 360f / shuffledDevices.Count;
|
||||
@@ -223,22 +239,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
public async Task ViewProperties(ArtemisDevice device)
|
||||
public void ViewProperties(ArtemisDevice device)
|
||||
{
|
||||
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceConfigViewModel>(
|
||||
new Dictionary<string, object> {{"device", device}}
|
||||
);
|
||||
|
||||
if ((bool) madeChanges)
|
||||
_rgbService.SaveDevice(device);
|
||||
_windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(device));
|
||||
}
|
||||
|
||||
public async Task DetectInput(ArtemisDevice device)
|
||||
{
|
||||
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>(
|
||||
new Dictionary<string, object> {{"device", device}}
|
||||
);
|
||||
|
||||
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>(new Dictionary<string, object> {{"device", device}});
|
||||
if ((bool) madeChanges)
|
||||
_rgbService.SaveDevice(device);
|
||||
}
|
||||
@@ -249,6 +257,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
|
||||
private MouseDragStatus _mouseDragStatus;
|
||||
private Point _mouseDragStartPoint;
|
||||
private bool _colorDevices;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
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.Manufacturer}" Foreground="{DynamicResource MaterialDesignBodyLight}" />
|
||||
</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>
|
||||
<SolidColorBrush Color="{Binding Color, Converter={StaticResource SKColorToColorConverter}}" />
|
||||
</Ellipse.Fill>
|
||||
|
||||
@@ -6,25 +6,28 @@ namespace Artemis.UI.Screens.SurfaceEditor.Visualization
|
||||
{
|
||||
public class ListDeviceViewModel : PropertyChangedBase
|
||||
{
|
||||
private bool _isSelected;
|
||||
private SKColor _color;
|
||||
public ArtemisDevice Device { get; }
|
||||
private bool _isSelected;
|
||||
|
||||
public ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
|
||||
{
|
||||
Device = device;
|
||||
Parent = surfaceEditorViewModel;
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
public SurfaceEditorViewModel Parent { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetAndNotify(ref _isSelected, value);
|
||||
set => SetAndNotify(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public SKColor Color
|
||||
{
|
||||
get => _color;
|
||||
set => SetAndNotify(ref _color, value);
|
||||
}
|
||||
|
||||
public ListDeviceViewModel(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,25 @@
|
||||
<controls:MaterialWindow x:Class="Artemis.UI.Screens.TrayView"
|
||||
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:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
|
||||
mc:Ignorable="d"
|
||||
Title="Artemis"
|
||||
Height="1"
|
||||
Width="1"
|
||||
Visibility="Hidden">
|
||||
<tb:TaskbarIcon x:Name="TrayIcon"
|
||||
IconSource="/Resources/Images/Logo/logo-512.ico"
|
||||
<Window x:Class="Artemis.UI.Screens.TrayView"
|
||||
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:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d">
|
||||
<tb:TaskbarIcon IconSource="/Resources/Images/Logo/logo-512.ico"
|
||||
MenuActivation="LeftOrRightClick"
|
||||
PopupActivation="DoubleClick"
|
||||
ToolTipText="Artemis"
|
||||
DoubleClickCommand="{s:Action TrayBringToForeground}"
|
||||
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>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Home" Command="{s:Action TrayActivateSidebarItem}" CommandParameter="Home">
|
||||
@@ -60,4 +61,4 @@
|
||||
</ContextMenu>
|
||||
</tb:TaskbarIcon.ContextMenu>
|
||||
</tb:TaskbarIcon>
|
||||
</controls:MaterialWindow>
|
||||
</Window>
|
||||
@@ -4,9 +4,9 @@ using Artemis.Core.Services;
|
||||
using Artemis.UI.Controllers;
|
||||
using Artemis.UI.DefaultTypes.DataModel.Display;
|
||||
using Artemis.UI.DefaultTypes.DataModel.Input;
|
||||
using Artemis.UI.DefaultTypes.PropertyInput;
|
||||
using Artemis.UI.InputProviders;
|
||||
using Artemis.UI.Ninject;
|
||||
using Artemis.UI.PropertyInput;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Serilog;
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ using Artemis.UI.Shared.Services;
|
||||
using Flurl;
|
||||
using Flurl.Http;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
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 readonly PluginSetting<bool> _autoInstallUpdates;
|
||||
private readonly PluginSetting<bool> _checkForUpdates;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly IWindowService _windowService;
|
||||
|
||||
@@ -46,6 +45,32 @@ namespace Artemis.UI.Services
|
||||
_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()
|
||||
{
|
||||
if (!_checkForUpdates.Value)
|
||||
@@ -68,7 +93,9 @@ namespace Artemis.UI.Services
|
||||
return false;
|
||||
|
||||
if (_windowService.IsMainWindowOpen)
|
||||
{
|
||||
await OfferUpdate(buildInfo);
|
||||
}
|
||||
else if (_autoInstallUpdates.Value)
|
||||
{
|
||||
// Lets go
|
||||
@@ -92,11 +119,6 @@ namespace Artemis.UI.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task OfferUpdate(DevOpsBuild buildInfo)
|
||||
{
|
||||
await _dialogService.ShowDialog<UpdateDialogViewModel>(new Dictionary<string, object> {{"buildInfo", buildInfo}});
|
||||
}
|
||||
|
||||
public async Task<bool> IsUpdateAvailable()
|
||||
{
|
||||
DevOpsBuild buildInfo = await GetBuildInfo(1);
|
||||
@@ -110,11 +132,13 @@ namespace Artemis.UI.Services
|
||||
|
||||
// Ensure the installer is up-to-date, get installer build info
|
||||
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 ^^
|
||||
if (!File.Exists(installerPath))
|
||||
{
|
||||
await UpdateInstaller();
|
||||
}
|
||||
// Compare the creation date of the installer with the build date and update if needed
|
||||
else
|
||||
{
|
||||
@@ -182,27 +206,6 @@ namespace Artemis.UI.Services
|
||||
.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
|
||||
|
||||
private void CheckForUpdatesOnSettingChanged(object sender, EventArgs e)
|
||||
|
||||
@@ -27,9 +27,13 @@
|
||||
},
|
||||
"Hardcodet.NotifyIcon.Wpf.NetCore": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.14, )",
|
||||
"resolved": "1.0.14",
|
||||
"contentHash": "aNwwax4+C/xhIxTbwKHJnxh0A6hWGnqXZtppZ+/lkE8VOL+a2WuMOBgWpkiUk6Tv+pi1bc+yuHo0lh4Md6KEFw=="
|
||||
"requested": "[1.0.18, )",
|
||||
"resolved": "1.0.18",
|
||||
"contentHash": "oI8YY/gUQooA0XIIZl4TgueexJcu+MbSvCQ2+ZBZRa+rIvFCWiyk8rjgywQ17sVrhLXRn+xF8+FTrWLxetkx0A==",
|
||||
"dependencies": {
|
||||
"H.NotifyIcon": "1.0.18",
|
||||
"System.Drawing.Common": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core": {
|
||||
"type": "Direct",
|
||||
@@ -225,6 +229,11 @@
|
||||
"resolved": "3.0.1",
|
||||
"contentHash": "i7CuPSikVroBaWG8sPvO707Ex9C6BP5+r4JufKNU1FGMmiFgLJvNo1ttUg6ZiXIzUNknvIb1VUTIO9iEDucibg=="
|
||||
},
|
||||
"H.NotifyIcon": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.18",
|
||||
"contentHash": "vV0lNWD9xGeCH4pCmT8vKtax2QOmo8WwhCBgBDO3BYQtPG7Rjuf5Ua+3O8++XkZB7t9zromzxcT5nYdGDg1Puw=="
|
||||
},
|
||||
"HidSharp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.0",
|
||||
|
||||
Reference in new issue
Block a user