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

Merge branch 'development'

This commit is contained in:
Robert 2021-04-06 20:59:13 +02:00
commit 0e76ec31a3
58 changed files with 1432 additions and 863 deletions

View File

@ -1,4 +1,5 @@
using System.ComponentModel; using System.Collections.Specialized;
using System.ComponentModel;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
@ -23,17 +24,16 @@ namespace Artemis.Core
if (CurrentValue == null) if (CurrentValue == null)
return; return;
for (int index = 0; index < CurrentValue.Stops.Count; index++) for (int index = 0; index < CurrentValue.Count; index++)
{ {
int stopIndex = index; int stopIndex = index;
void Setter(SKColor value) void Setter(SKColor value)
{ {
CurrentValue.Stops[stopIndex].Color = value; CurrentValue[stopIndex].Color = value;
CurrentValue.OnColorValuesUpdated();
} }
RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}"); RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
} }
} }
@ -61,17 +61,17 @@ namespace Artemis.Core
if (_subscribedGradient != BaseValue) if (_subscribedGradient != BaseValue)
{ {
if (_subscribedGradient != null) if (_subscribedGradient != null)
_subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged; _subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
_subscribedGradient = BaseValue; _subscribedGradient = BaseValue;
_subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged; _subscribedGradient.CollectionChanged += SubscribedGradientOnPropertyChanged;
} }
CreateDataBindingRegistrations(); CreateDataBindingRegistrations();
} }
private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e) private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
{ {
if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count) if (CurrentValue.Count != GetAllDataBindingRegistrations().Count)
CreateDataBindingRegistrations(); CreateDataBindingRegistrations();
} }

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq; using System.Linq;
using SkiaSharp; using SkiaSharp;
@ -8,7 +11,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// A gradient containing a list of <see cref="ColorGradientStop" />s /// A gradient containing a list of <see cref="ColorGradientStop" />s
/// </summary> /// </summary>
public class ColorGradient : CorePropertyChanged public class ColorGradient : IList<ColorGradientStop>, INotifyCollectionChanged
{ {
private static readonly SKColor[] FastLedRainbow = private static readonly SKColor[] FastLedRainbow =
{ {
@ -23,19 +26,16 @@ namespace Artemis.Core
new(0xFFFF0000) // and back to Red new(0xFFFF0000) // and back to Red
}; };
private readonly List<ColorGradientStop> _stops;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ColorGradient" /> class /// Creates a new instance of the <see cref="ColorGradient" /> class
/// </summary> /// </summary>
public ColorGradient() public ColorGradient()
{ {
Stops = new List<ColorGradientStop>(); _stops = new List<ColorGradientStop>();
} }
/// <summary>
/// Gets a list of all the <see cref="ColorGradientStop" />s in the gradient
/// </summary>
public List<ColorGradientStop> Stops { get; }
/// <summary> /// <summary>
/// Gets all the colors in the color gradient /// Gets all the colors in the color gradient
/// </summary> /// </summary>
@ -49,13 +49,16 @@ namespace Artemis.Core
{ {
List<SKColor> result = new(); List<SKColor> result = new();
if (timesToRepeat == 0) if (timesToRepeat == 0)
result = Stops.Select(c => c.Color).ToList(); {
result = this.Select(c => c.Color).ToList();
}
else else
{ {
List<SKColor> colors = Stops.Select(c => c.Color).ToList(); List<SKColor> colors = this.Select(c => c.Color).ToList();
for (int i = 0; i <= timesToRepeat; i++) for (int i = 0; i <= timesToRepeat; i++)
result.AddRange(colors); result.AddRange(colors);
} }
if (seamless && !IsSeamless()) if (seamless && !IsSeamless())
result.Add(result[0]); result.Add(result[0]);
@ -77,11 +80,13 @@ namespace Artemis.Core
{ {
List<float> result = new(); List<float> result = new();
if (timesToRepeat == 0) if (timesToRepeat == 0)
result = Stops.Select(c => c.Position).ToList(); {
result = this.Select(c => c.Position).ToList();
}
else else
{ {
// Create stops and a list of divided stops // Create stops and a list of divided stops
List<float> stops = Stops.Select(c => c.Position / (timesToRepeat + 1)).ToList(); List<float> stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
// For each repeat cycle, add the base stops to the end result // For each repeat cycle, add the base stops to the end result
for (int i = 0; i <= timesToRepeat; i++) for (int i = 0; i <= timesToRepeat; i++)
@ -104,25 +109,16 @@ namespace Artemis.Core
return result.ToArray(); return result.ToArray();
} }
/// <summary>
/// Triggers a property changed event of the <see cref="Stops" /> collection
/// </summary>
public void OnColorValuesUpdated()
{
Stops.Sort((a, b) => a.Position.CompareTo(b.Position));
OnPropertyChanged(nameof(Stops));
}
/// <summary> /// <summary>
/// Gets a color at any position between 0.0 and 1.0 using interpolation /// Gets a color at any position between 0.0 and 1.0 using interpolation
/// </summary> /// </summary>
/// <param name="position">A position between 0.0 and 1.0</param> /// <param name="position">A position between 0.0 and 1.0</param>
public SKColor GetColor(float position) public SKColor GetColor(float position)
{ {
if (!Stops.Any()) if (!this.Any())
return SKColor.Empty; return SKColor.Empty;
ColorGradientStop[] stops = Stops.ToArray(); ColorGradientStop[] stops = this.ToArray();
if (position <= 0) return stops[0].Color; if (position <= 0) return stops[0].Color;
if (position >= 1) return stops[^1].Color; if (position >= 1) return stops[^1].Color;
ColorGradientStop left = stops[0]; ColorGradientStop left = stops[0];
@ -160,7 +156,7 @@ namespace Artemis.Core
{ {
SKColor skColor = FastLedRainbow[index]; SKColor skColor = FastLedRainbow[index];
float position = 1f / (FastLedRainbow.Length - 1f) * index; float position = 1f / (FastLedRainbow.Length - 1f) * index;
gradient.Stops.Add(new ColorGradientStop(skColor, position)); gradient.Add(new ColorGradientStop(skColor, position));
} }
return gradient; return gradient;
@ -172,7 +168,141 @@ namespace Artemis.Core
/// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns> /// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns>
public bool IsSeamless() public bool IsSeamless()
{ {
return Stops.Count == 0 || Stops.First().Color.Equals(Stops.Last().Color); return Count == 0 || this.First().Color.Equals(this.Last().Color);
}
internal void Sort()
{
_stops.Sort((a, b) => a.Position.CompareTo(b.Position));
}
private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Sort();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#region Implementation of IEnumerable
/// <inheritdoc />
public IEnumerator<ColorGradientStop> GetEnumerator()
{
return _stops.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection<ColorGradientStop>
/// <inheritdoc />
public void Add(ColorGradientStop item)
{
_stops.Add(item);
item.PropertyChanged += ItemOnPropertyChanged;
Sort();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _stops.IndexOf(item)));
}
/// <inheritdoc />
public void Clear()
{
foreach (ColorGradientStop item in _stops)
item.PropertyChanged -= ItemOnPropertyChanged;
_stops.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <inheritdoc />
public bool Contains(ColorGradientStop item)
{
return _stops.Contains(item);
}
/// <inheritdoc />
public void CopyTo(ColorGradientStop[] array, int arrayIndex)
{
_stops.CopyTo(array, arrayIndex);
}
/// <inheritdoc />
public bool Remove(ColorGradientStop item)
{
item.PropertyChanged -= ItemOnPropertyChanged;
int index = _stops.IndexOf(item);
bool removed = _stops.Remove(item);
if (removed)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
return removed;
}
/// <inheritdoc />
public int Count => _stops.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
#endregion
#region Implementation of IList<ColorGradientStop>
/// <inheritdoc />
public int IndexOf(ColorGradientStop item)
{
return _stops.IndexOf(item);
}
/// <inheritdoc />
public void Insert(int index, ColorGradientStop item)
{
_stops.Insert(index, item);
item.PropertyChanged += ItemOnPropertyChanged;
Sort();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
}
/// <inheritdoc />
public void RemoveAt(int index)
{
_stops[index].PropertyChanged -= ItemOnPropertyChanged;
_stops.RemoveAt(index);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
}
/// <inheritdoc />
public ColorGradientStop this[int index]
{
get => _stops[index];
set
{
ColorGradientStop? oldValue = _stops[index];
oldValue.PropertyChanged -= ItemOnPropertyChanged;
_stops[index] = value;
_stops[index].PropertyChanged += ItemOnPropertyChanged;
Sort();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
} }
} }
#endregion
#region Implementation of INotifyCollectionChanged
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler? CollectionChanged;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
CollectionChanged?.Invoke(this, e);
}
#endregion
}
} }

View File

@ -297,6 +297,9 @@ namespace Artemis.Core
private void ApplyTimeline(Timeline timeline) private void ApplyTimeline(Timeline timeline)
{ {
if (timeline.Delta == TimeSpan.Zero)
return;
General.Update(timeline); General.Update(timeline);
Transform.Update(timeline); Transform.Update(timeline);
if (LayerBrush != null) if (LayerBrush != null)

View File

@ -33,6 +33,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
Type PropertyType { get; } Type PropertyType { get; }
/// <summary>
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
/// </summary>
bool IsLoadedFromStorage { get; }
/// <summary> /// <summary>
/// Initializes the layer property /// Initializes the layer property
/// <para> /// <para>
@ -54,6 +59,11 @@ namespace Artemis.Core
/// <returns>If succeeded the resulting keyframe, otherwise <see langword="null" /></returns> /// <returns>If succeeded the resulting keyframe, otherwise <see langword="null" /></returns>
ILayerPropertyKeyframe? AddKeyframeEntity(KeyframeEntity keyframeEntity); ILayerPropertyKeyframe? AddKeyframeEntity(KeyframeEntity keyframeEntity);
/// <summary>
/// Overrides the property value with the default value
/// </summary>
void ApplyDefaultValue();
/// <summary> /// <summary>
/// Updates the layer properties internal state /// Updates the layer properties internal state
/// </summary> /// </summary>

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -39,6 +38,107 @@ namespace Artemis.Core
_keyframes = new List<LayerPropertyKeyframe<T>>(); _keyframes = new List<LayerPropertyKeyframe<T>>();
} }
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet()
{
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved()
{
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>
protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e)
{
DataBindingEnabled?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e)
{
DataBindingDisabled?.Invoke(this, e);
}
/// <inheritdoc /> /// <inheritdoc />
public PropertyDescriptionAttribute PropertyDescription { get; internal set; } public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
@ -62,28 +162,6 @@ namespace Artemis.Core
OnUpdated(); OnUpdated();
} }
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
@ -91,7 +169,38 @@ namespace Artemis.Core
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#endregion /// <inheritdoc />
public event EventHandler? Disposed;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? Updated;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingDisabled;
#region Hierarchy #region Hierarchy
@ -195,22 +304,25 @@ namespace Artemis.Core
ReapplyUpdate(); ReapplyUpdate();
} }
/// <summary> /// <inheritdoc />
/// Overrides the property value with the default value public void ApplyDefaultValue()
/// </summary>
public void ApplyDefaultValue(TimeSpan? time)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
string json = CoreJson.SerializeObject(DefaultValue, true); string json = CoreJson.SerializeObject(DefaultValue, true);
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!, time); KeyframesEnabled = false;
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!, null);
} }
private void ReapplyUpdate() private void ReapplyUpdate()
{ {
ProfileElement.Timeline.ClearDelta(); // Create a timeline with the same position but a delta of zero
Update(ProfileElement.Timeline); Timeline temporaryTimeline = new();
temporaryTimeline.Override(ProfileElement.Timeline.Position, false);
temporaryTimeline.ClearDelta();
Update(temporaryTimeline);
OnCurrentValueSet(); OnCurrentValueSet();
} }
@ -593,7 +705,7 @@ namespace Artemis.Core
throw new ArtemisCoreException("Layer property is not yet initialized"); throw new ArtemisCoreException("Layer property is not yet initialized");
if (!IsLoadedFromStorage) if (!IsLoadedFromStorage)
ApplyDefaultValue(null); ApplyDefaultValue();
else else
try try
{ {
@ -657,123 +769,5 @@ namespace Artemis.Core
} }
#endregion #endregion
#region Events
/// <inheritdoc />
public event EventHandler? Disposed;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? Updated;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingDisabled;
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet()
{
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved()
{
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>
protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e)
{
DataBindingEnabled?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e)
{
DataBindingDisabled?.Invoke(this, e);
}
#endregion
} }
} }

View File

@ -121,6 +121,31 @@ namespace Artemis.Core
return result.AsReadOnly(); return result.AsReadOnly();
} }
/// <summary>
/// Applies the default value to all layer properties
/// </summary>
public void ResetAllLayerProperties()
{
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.ApplyDefaultValue();
}
/// <summary>
/// Occurs when the property group has initialized all its children
/// </summary>
public event EventHandler? PropertyGroupInitialized;
/// <summary>
/// Occurs when one of the current value of one of the layer properties in this group changes by some form of input
/// <para>Note: Will not trigger on properties in child groups</para>
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? LayerPropertyOnCurrentValueSet;
/// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary>
public event EventHandler? VisibilityChanged;
/// <summary> /// <summary>
/// Called before property group is activated to allow you to populate <see cref="LayerProperty{T}.DefaultValue" /> on /// Called before property group is activated to allow you to populate <see cref="LayerProperty{T}.DefaultValue" /> on
/// the properties you want /// the properties you want
@ -145,6 +170,27 @@ namespace Artemis.Core
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); PropertyGroupInitialized?.Invoke(this, EventArgs.Empty);
} }
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
DisableProperties();
foreach (ILayerProperty layerProperty in _layerProperties)
layerProperty.Dispose();
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
layerPropertyGroup.Dispose();
}
}
internal void Initialize(RenderProfileElement profileElement, string path, PluginFeature feature) internal void Initialize(RenderProfileElement profileElement, string path, PluginFeature feature)
{ {
if (path == null) throw new ArgumentNullException(nameof(path)); if (path == null) throw new ArgumentNullException(nameof(path));
@ -209,6 +255,17 @@ namespace Artemis.Core
layerPropertyGroup.Update(timeline); layerPropertyGroup.Update(timeline);
} }
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
}
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
{
Parent?.OnLayerPropertyOnCurrentValueSet(e);
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
}
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription) private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
{ {
string path = $"{Path}.{propertyInfo.Name}"; string path = $"{Path}.{propertyInfo.Name}";
@ -266,67 +323,11 @@ namespace Artemis.Core
return entity; return entity;
} }
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
DisableProperties();
foreach (ILayerProperty layerProperty in _layerProperties)
layerProperty.Dispose();
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
layerPropertyGroup.Dispose();
}
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#endregion
#region Events
/// <summary>
/// Occurs when the property group has initialized all its children
/// </summary>
public event EventHandler? PropertyGroupInitialized;
/// <summary>
/// Occurs when one of the current value of one of the layer properties in this group changes by some form of input
/// <para>Note: Will not trigger on properties in child groups</para>
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? LayerPropertyOnCurrentValueSet;
/// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary>
public event EventHandler? VisibilityChanged;
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
}
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
{
Parent?.OnLayerPropertyOnCurrentValueSet(e);
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
}
#endregion
} }
} }

View File

@ -329,6 +329,13 @@ namespace Artemis.Core
/// <param name="removeExcessiveLeds">A boolean indicating whether to remove excess LEDs present in the device but missing in the layout</param> /// <param name="removeExcessiveLeds">A boolean indicating whether to remove excess LEDs present in the device but missing in the layout</param>
internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds) internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds)
{ {
if (createMissingLeds && !DeviceProvider.CreateMissingLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} " +
"set to true because the device provider does not support it");
if (removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} " +
"set to true because the device provider does not support it");
if (layout.IsValid) if (layout.IsValid)
layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds); layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);

View File

@ -51,6 +51,18 @@ namespace Artemis.Core.DeviceProviders
/// </summary> /// </summary>
public bool CanDetectLogicalLayout { get; protected set; } public bool CanDetectLogicalLayout { get; protected set; }
/// <summary>
/// Gets or sets a boolean indicating whether adding missing LEDs defined in a layout but missing on the device is supported
/// <para>Note: Defaults to <see langword="true" />.</para>
/// </summary>
public bool CreateMissingLedsSupported { get; protected set; } = true;
/// <summary>
/// Gets or sets a boolean indicating whether removing excess LEDs present in the device but missing in the layout is supported
/// <para>Note: Defaults to <see langword="true" />.</para>
/// </summary>
public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
/// <summary> /// <summary>
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" /> /// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary> /// </summary>

View File

@ -0,0 +1,28 @@
namespace Artemis.Core.LayerBrushes
{
/// <summary>
/// Represents a brush preset for a brush.
/// </summary>
public interface ILayerBrushPreset
{
/// <summary>
/// Gets the name of the preset
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of the preset
/// </summary>
string Description { get; }
/// <summary>
/// Gets the icon of the preset
/// </summary>
string Icon { get; }
/// <summary>
/// Applies the preset to the layer brush
/// </summary>
void Apply();
}
}

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core.LayerBrushes namespace Artemis.Core.LayerBrushes
@ -70,6 +72,16 @@ namespace Artemis.Core.LayerBrushes
/// </summary> /// </summary>
public virtual LayerPropertyGroup? BaseProperties => null; public virtual LayerPropertyGroup? BaseProperties => null;
/// <summary>
/// Gets a list of presets available to this layer brush
/// </summary>
public virtual List<ILayerBrushPreset>? Presets => null;
/// <summary>
/// Gets the default preset used for new instances of this layer brush
/// </summary>
public virtual ILayerBrushPreset? DefaultPreset => Presets?.FirstOrDefault();
/// <summary> /// <summary>
/// Gets or sets whether the brush supports transformations /// Gets or sets whether the brush supports transformations
/// <para>Note: RGB.NET brushes can never be transformed and setting this to true will throw an exception</para> /// <para>Note: RGB.NET brushes can never be transformed and setting this to true will throw an exception</para>

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core.LayerBrushes namespace Artemis.Core.LayerBrushes
{ {

View File

@ -292,7 +292,7 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"FeatureId": null, "FeatureId": null,
"Path": "LayerBrush.GradientColor", "Path": "LayerBrush.GradientColor",
"Value": "{\"Stops\":[{\"Color\":\"#ff0b4a40\",\"Position\":0.0},{\"Color\":\"#ff00897c\",\"Position\":0.242},{\"Color\":\"#ffffffff\",\"Position\":1.0},{\"Color\":\"#ff00ffe6\",\"Position\":0.67391306}]}", "Value": "[{\"Color\":\"#ff0b4a40\",\"Position\":0.0},{\"Color\":\"#ff00897c\",\"Position\":0.242},{\"Color\":\"#ffffffff\",\"Position\":1.0},{\"Color\":\"#ff00ffe6\",\"Position\":0.67391306}]",
"KeyframesEnabled": false, "KeyframesEnabled": false,
"KeyframeEntities": { "KeyframeEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
@ -592,7 +592,7 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"FeatureId": "Artemis.Plugins.LayerBrushes.Noise.NoiseBrushProvider-61cbbf01", "FeatureId": "Artemis.Plugins.LayerBrushes.Noise.NoiseBrushProvider-61cbbf01",
"Path": "LayerBrush.GradientColor", "Path": "LayerBrush.GradientColor",
"Value": "{\"Stops\":[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffffbf00\",\"Position\":0.125},{\"Color\":\"#ff7fff00\",\"Position\":0.25},{\"Color\":\"#ff00ff3f\",\"Position\":0.375},{\"Color\":\"#ff00ffff\",\"Position\":0.5},{\"Color\":\"#ff003fff\",\"Position\":0.625},{\"Color\":\"#ff7f00ff\",\"Position\":0.75},{\"Color\":\"#ffff00bf\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]}", "Value": "[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffffbf00\",\"Position\":0.125},{\"Color\":\"#ff7fff00\",\"Position\":0.25},{\"Color\":\"#ff00ff3f\",\"Position\":0.375},{\"Color\":\"#ff00ffff\",\"Position\":0.5},{\"Color\":\"#ff003fff\",\"Position\":0.625},{\"Color\":\"#ff7f00ff\",\"Position\":0.75},{\"Color\":\"#ffff00bf\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]",
"KeyframesEnabled": false, "KeyframesEnabled": false,
"KeyframeEntities": { "KeyframeEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
@ -909,7 +909,7 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"FeatureId": null, "FeatureId": null,
"Path": "LayerBrush.Colors", "Path": "LayerBrush.Colors",
"Value": "{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffffdf00\",\"Position\":0.916},{\"Color\":\"#00ffcd00\",\"Position\":1.0},{\"Color\":\"#fff31900\",\"Position\":0.636},{\"Color\":\"#dbf41500\",\"Position\":0.5461956},{\"Color\":\"#00f60f00\",\"Position\":0.462},{\"Color\":\"#93f60d00\",\"Position\":0.3668478}]}", "Value": "[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffffdf00\",\"Position\":0.916},{\"Color\":\"#00ffcd00\",\"Position\":1.0},{\"Color\":\"#fff31900\",\"Position\":0.636},{\"Color\":\"#dbf41500\",\"Position\":0.5461956},{\"Color\":\"#00f60f00\",\"Position\":0.462},{\"Color\":\"#93f60d00\",\"Position\":0.3668478}]",
"KeyframesEnabled": false, "KeyframesEnabled": false,
"KeyframeEntities": { "KeyframeEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
@ -1164,7 +1164,7 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"FeatureId": "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba", "FeatureId": "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
"Path": "LayerBrush.Colors", "Path": "LayerBrush.Colors",
"Value": "{\"Stops\":[{\"Color\":\"#ffcaff00\",\"Position\":0.709},{\"Color\":\"#ffff0000\",\"Position\":0.902},{\"Color\":\"#ffffffff\",\"Position\":0.5842391},{\"Color\":\"#ffff4100\",\"Position\":0.95108694},{\"Color\":\"#00ff4100\",\"Position\":0.98641306}]}", "Value": "[{\"Color\":\"#ffcaff00\",\"Position\":0.709},{\"Color\":\"#ffff0000\",\"Position\":0.902},{\"Color\":\"#ffffffff\",\"Position\":0.5842391},{\"Color\":\"#ffff4100\",\"Position\":0.95108694},{\"Color\":\"#00ff4100\",\"Position\":0.98641306}]",
"KeyframesEnabled": false, "KeyframesEnabled": false,
"KeyframeEntities": { "KeyframeEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
@ -1496,7 +1496,7 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"FeatureId": null, "FeatureId": null,
"Path": "LayerBrush.Colors", "Path": "LayerBrush.Colors",
"Value": "{\"Stops\":[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.125},{\"Color\":\"#ffedff00\",\"Position\":0.25},{\"Color\":\"#ff65ff00\",\"Position\":0.375},{\"Color\":\"#ff00ff22\",\"Position\":0.5},{\"Color\":\"#ff00ffaa\",\"Position\":0.625},{\"Color\":\"#ff00cbff\",\"Position\":0.75},{\"Color\":\"#ff0043ff\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]}", "Value": "[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.125},{\"Color\":\"#ffedff00\",\"Position\":0.25},{\"Color\":\"#ff65ff00\",\"Position\":0.375},{\"Color\":\"#ff00ff22\",\"Position\":0.5},{\"Color\":\"#ff00ffaa\",\"Position\":0.625},{\"Color\":\"#ff00cbff\",\"Position\":0.75},{\"Color\":\"#ff0043ff\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]",
"KeyframesEnabled": false, "KeyframesEnabled": false,
"KeyframeEntities": { "KeyframeEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
@ -1751,7 +1751,7 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage", "$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"FeatureId": "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba", "FeatureId": "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
"Path": "LayerBrush.Colors", "Path": "LayerBrush.Colors",
"Value": "{\"Stops\":[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffffbf00\",\"Position\":0.125},{\"Color\":\"#ff7fff00\",\"Position\":0.25},{\"Color\":\"#ff00ff3f\",\"Position\":0.375},{\"Color\":\"#ff00ffff\",\"Position\":0.5},{\"Color\":\"#ff003fff\",\"Position\":0.625},{\"Color\":\"#ff7f00ff\",\"Position\":0.75},{\"Color\":\"#ffff00bf\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]}", "Value": "[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffffbf00\",\"Position\":0.125},{\"Color\":\"#ff7fff00\",\"Position\":0.25},{\"Color\":\"#ff00ff3f\",\"Position\":0.375},{\"Color\":\"#ff00ffff\",\"Position\":0.5},{\"Color\":\"#ff003fff\",\"Position\":0.625},{\"Color\":\"#ff7f00ff\",\"Position\":0.75},{\"Color\":\"#ffff00bf\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]",
"KeyframesEnabled": false, "KeyframesEnabled": false,
"KeyframeEntities": { "KeyframeEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib", "$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",

View File

@ -251,20 +251,16 @@ namespace Artemis.Core.Services
// Draw a white overlay over the device // Draw a white overlay over the device
void DrawOverlay(object? sender, FrameRenderingEventArgs args) void DrawOverlay(object? sender, FrameRenderingEventArgs args)
{ {
if (intro.AnimationProfile.GetAllLayers().All(l => l.Timeline.IsFinished))
{
FrameRendering -= DrawOverlay;
intro.AnimationProfile.Dispose();
}
intro.Render(args.DeltaTime, args.Canvas); intro.Render(args.DeltaTime, args.Canvas);
} }
FrameRendering += DrawOverlay; FrameRendering += DrawOverlay;
// Stop rendering after the profile finishes (take 1 second extra in case of slow updates)
TimeSpan introLength = intro.AnimationProfile.GetAllLayers().Max(l => l.Timeline.Length)!;
Task.Run(async () =>
{
await Task.Delay(introLength.Add(TimeSpan.FromSeconds(1)));
FrameRendering -= DrawOverlay;
intro.AnimationProfile.Dispose();
});
} }
public event EventHandler? Initialized; public event EventHandler? Initialized;

View File

@ -90,9 +90,7 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
/// <param name="device"></param> /// <param name="device"></param>
/// <param name="layout"></param> /// <param name="layout"></param>
/// <param name="createMissingLeds"></param> void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <param name="removeExessiveLeds"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds);
/// <summary> /// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET /// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET

View File

@ -312,7 +312,7 @@ namespace Artemis.Core.Services
layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured); layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured);
if (layout.IsValid) if (layout.IsValid)
{ {
ApplyDeviceLayout(device, layout, true, true); ApplyDeviceLayout(device, layout);
return layout; return layout;
} }
} }
@ -321,7 +321,7 @@ namespace Artemis.Core.Services
layout = device.DeviceProvider.LoadUserLayout(device); layout = device.DeviceProvider.LoadUserLayout(device);
if (layout.IsValid) if (layout.IsValid)
{ {
ApplyDeviceLayout(device, layout, true, true); ApplyDeviceLayout(device, layout);
return layout; return layout;
} }
@ -329,13 +329,13 @@ namespace Artemis.Core.Services
layout = device.DeviceProvider.LoadLayout(device); layout = device.DeviceProvider.LoadLayout(device);
if (layout.IsValid) if (layout.IsValid)
{ {
ApplyDeviceLayout(device, layout, true, true); ApplyDeviceLayout(device, layout);
return layout; return layout;
} }
// Finally fall back to a default layout // Finally fall back to a default layout
layout = LoadDefaultLayout(device); layout = LoadDefaultLayout(device);
ApplyDeviceLayout(device, layout, true, true); ApplyDeviceLayout(device, layout);
return layout; return layout;
} }
@ -344,9 +344,9 @@ namespace Artemis.Core.Services
return new("NYI", LayoutSource.Default); return new("NYI", LayoutSource.Default);
} }
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds) public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout)
{ {
device.ApplyLayout(layout, createMissingLeds, removeExessiveLeds); device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
UpdateLedGroup(); UpdateLedGroup();
} }

View File

@ -55,7 +55,8 @@ namespace Artemis.Core.Services
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel; DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
/// <summary> /// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="profileModule" />. /// Adds a new endpoint that directly maps received JSON to the data model of the provided
/// <paramref name="profileModule" />.
/// </summary> /// </summary>
/// <typeparam name="T">The data model type of the module</typeparam> /// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="profileModule">The module whose datamodel to apply the received JSON to</param> /// <param name="profileModule">The module whose datamodel to apply the received JSON to</param>
@ -64,7 +65,8 @@ namespace Artemis.Core.Services
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel; DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel;
/// <summary> /// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="dataModelExpansion" />. /// Adds a new endpoint that directly maps received JSON to the data model of the provided
/// <paramref name="dataModelExpansion" />.
/// </summary> /// </summary>
/// <typeparam name="T">The data model type of the module</typeparam> /// <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="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
@ -114,7 +116,7 @@ namespace Artemis.Core.Services
/// Adds a new Web API controller and restarts the web server /// Adds a new Web API controller and restarts the web server
/// </summary> /// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam> /// <typeparam name="T">The type of Web API controller to remove</typeparam>
void AddController<T>() where T : WebApiController; void AddController<T>(PluginFeature feature) where T : WebApiController;
/// <summary> /// <summary>
/// Removes an existing Web API controller and restarts the web server /// Removes an existing Web API controller and restarts the web server
@ -122,6 +124,18 @@ namespace Artemis.Core.Services
/// <typeparam name="T">The type of Web API controller to remove</typeparam> /// <typeparam name="T">The type of Web API controller to remove</typeparam>
void RemoveController<T>() where T : WebApiController; void RemoveController<T>() where T : WebApiController;
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to add</typeparam>
void AddModule<T>(PluginFeature feature) where T : IWebModule;
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to remove</typeparam>
void RemoveModule<T>() where T : IWebModule;
/// <summary> /// <summary>
/// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules. /// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules.
/// </summary> /// </summary>

View File

@ -48,13 +48,13 @@ namespace Artemis.Core.Services
protected override async Task OnRequestAsync(IHttpContext context) protected override async Task OnRequestAsync(IHttpContext context)
{ {
if (context.Route.SubPath == null) if (context.Route.SubPath == null)
throw HttpException.NotFound(); return;
// Split the sub path // Split the sub path
string[] pathParts = context.Route.SubPath.Substring(1).Split('/'); string[] pathParts = context.Route.SubPath.Substring(1).Split('/');
// Expect a plugin ID and an endpoint // Expect a plugin ID and an endpoint
if (pathParts == null || pathParts.Length != 2) if (pathParts.Length != 2)
throw HttpException.BadRequest("Path must contain a plugin ID and endpoint and nothing else."); return;
// Find a matching plugin // Find a matching plugin
if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints)) if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints))
@ -78,7 +78,7 @@ namespace Artemis.Core.Services
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool IsFinalHandler => true; public override bool IsFinalHandler => false;
internal string? ServerUrl { get; set; } internal string? ServerUrl { get; set; }

View File

@ -6,9 +6,9 @@ namespace Artemis.Core.Services
{ {
internal class WebApiControllerRegistration<T> : WebApiControllerRegistration where T : WebApiController internal class WebApiControllerRegistration<T> : WebApiControllerRegistration where T : WebApiController
{ {
public WebApiControllerRegistration(IKernel kernel) : base(typeof(T)) public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T))
{ {
Factory = () => kernel.Get<T>(); Factory = () => feature.Plugin.Kernel!.Get<T>();
} }
public Func<T> Factory { get; set; } public Func<T> Factory { get; set; }
@ -17,12 +17,14 @@ namespace Artemis.Core.Services
internal abstract class WebApiControllerRegistration internal abstract class WebApiControllerRegistration
{ {
protected WebApiControllerRegistration(Type controllerType) protected WebApiControllerRegistration(PluginFeature feature, Type controllerType)
{ {
Feature = feature;
ControllerType = controllerType; ControllerType = controllerType;
} }
public abstract object UntypedFactory { get; } public abstract object UntypedFactory { get; }
public Type ControllerType { get; set; } public Type ControllerType { get; set; }
public PluginFeature Feature { get; }
} }
} }

View File

@ -0,0 +1,20 @@
using System;
using EmbedIO;
using Ninject;
namespace Artemis.Core.Services
{
internal class WebModuleRegistration
{
public PluginFeature Feature { get; }
public Type WebModuleType { get; }
public WebModuleRegistration(PluginFeature feature, Type webModuleType)
{
Feature = feature;
WebModuleType = webModuleType;
}
public IWebModule CreateInstance() => (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType);
}
}

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
@ -8,7 +9,6 @@ using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using EmbedIO.WebApi; using EmbedIO.WebApi;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ninject;
using Serilog; using Serilog;
namespace Artemis.Core.Services namespace Artemis.Core.Services
@ -16,18 +16,19 @@ namespace Artemis.Core.Services
internal class WebServerService : IWebServerService, IDisposable internal class WebServerService : IWebServerService, IDisposable
{ {
private readonly List<WebApiControllerRegistration> _controllers; private readonly List<WebApiControllerRegistration> _controllers;
private readonly IKernel _kernel; private readonly List<WebModuleRegistration> _modules;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly PluginSetting<int> _webServerPortSetting; private readonly PluginSetting<int> _webServerPortSetting;
public WebServerService(IKernel kernel, ILogger logger, ISettingsService settingsService) public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{ {
_kernel = kernel;
_logger = logger; _logger = logger;
_controllers = new List<WebApiControllerRegistration>(); _controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>();
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696); _webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
PluginsModule = new PluginsModule("/plugins"); PluginsModule = new PluginsModule("/plugins");
StartWebServer(); StartWebServer();
@ -43,12 +44,18 @@ namespace Artemis.Core.Services
Server?.Dispose(); Server?.Dispose();
Server = null; Server = null;
WebApiModule apiModule = new("/api/", JsonNetSerializer); WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/"; PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO)) WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager() .WithLocalSessionManager()
.WithModule(PluginsModule);
// Add registered modules
foreach (var webModule in _modules)
server = server.WithModule(webModule.CreateInstance());
server = server
.WithModule(apiModule) .WithModule(apiModule)
.WithModule(PluginsModule)
.HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception)) .HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception))
.HandleUnhandledException(JsonExceptionHandlerCallback); .HandleUnhandledException(JsonExceptionHandlerCallback);
@ -166,9 +173,9 @@ namespace Artemis.Core.Services
#region Controller management #region Controller management
public void AddController<T>() where T : WebApiController public void AddController<T>(PluginFeature feature) where T : WebApiController
{ {
_controllers.Add(new WebApiControllerRegistration<T>(_kernel)); _controllers.Add(new WebApiControllerRegistration<T>(feature));
StartWebServer(); StartWebServer();
} }
@ -180,6 +187,26 @@ namespace Artemis.Core.Services
#endregion #endregion
#region Module management
public void AddModule<T>(PluginFeature feature) where T : IWebModule
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (_modules.Any(r => r.WebModuleType == typeof(T)))
return;
_modules.Add(new WebModuleRegistration(feature, typeof(T)));
StartWebServer();
}
public void RemoveModule<T>() where T : IWebModule
{
_modules.RemoveAll(r => r.WebModuleType == typeof(T));
StartWebServer();
}
#endregion
#region Handlers #region Handlers
private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception) private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception)
@ -235,6 +262,25 @@ namespace Artemis.Core.Services
StartWebServer(); StartWebServer();
} }
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
{
bool mustRestart = false;
if (_controllers.Any(c => c.Feature == e.PluginFeature))
{
mustRestart = true;
_controllers.RemoveAll(c => c.Feature == e.PluginFeature);
}
if (_modules.Any(m => m.Feature == e.PluginFeature))
{
mustRestart = true;
_modules.RemoveAll(m => m.Feature == e.PluginFeature);
}
if (mustRestart)
StartWebServer();
}
#endregion #endregion
#region IDisposable #region IDisposable

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Artemis.Core.Modules; using Artemis.Core.Modules;

View File

@ -3,7 +3,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M1AttributeBasedPropertiesMigration : IStorageMigration public class M0001AttributeBasedPropertiesMigration : IStorageMigration
{ {
public int UserVersion => 1; public int UserVersion => 1;

View File

@ -5,7 +5,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M2ProfileEntitiesEnabledMigration : IStorageMigration public class M0002ProfileEntitiesEnabledMigration : IStorageMigration
{ {
public int UserVersion => 2; public int UserVersion => 2;

View File

@ -3,7 +3,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M3PluginEntitiesIndexChangesMigration : IStorageMigration public class M0003PluginEntitiesIndexChangesMigration : IStorageMigration
{ {
public int UserVersion => 3; public int UserVersion => 3;

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M4ProfileSegmentsMigration : IStorageMigration public class M0004ProfileSegmentsMigration : IStorageMigration
{ {
public int UserVersion => 4; public int UserVersion => 4;

View File

@ -3,7 +3,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M5DataBindingTypes : IStorageMigration public class M0005DataBindingTypes : IStorageMigration
{ {
public int UserVersion => 5; public int UserVersion => 5;

View File

@ -4,7 +4,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M6PredicateAbstraction : IStorageMigration public class M0006PredicateAbstraction : IStorageMigration
{ {
public int UserVersion => 6; public int UserVersion => 6;

View File

@ -5,7 +5,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M8PluginFeatures : IStorageMigration public class M0008PluginFeatures : IStorageMigration
{ {
private void Migrate(BsonValue bsonValue, Dictionary<string, string> pluginMap) private void Migrate(BsonValue bsonValue, Dictionary<string, string> pluginMap)
{ {

View File

@ -3,7 +3,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M9DeviceCalibration : IStorageMigration public class M0009DeviceCalibration : IStorageMigration
{ {
public int UserVersion => 9; public int UserVersion => 9;

View File

@ -3,7 +3,7 @@ using LiteDB;
namespace Artemis.Storage.Migrations namespace Artemis.Storage.Migrations
{ {
public class M10BetterDataBindings : IStorageMigration public class M0010BetterDataBindings : IStorageMigration
{ {
private void Migrate(BsonValue bsonValue) private void Migrate(BsonValue bsonValue)
{ {

View File

@ -0,0 +1,42 @@
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations
{
public class M0011ColorGradients : IStorageMigration
{
private void Migrate(BsonValue bsonValue)
{
if (!bsonValue.IsDocument || !bsonValue.AsDocument.TryGetValue("PropertyEntities", out BsonValue propertyEntities))
return;
foreach (BsonValue propertyEntity in propertyEntities.AsArray)
{
string valueString = propertyEntity["Value"].AsString;
if (!valueString.StartsWith("{\"Stops\":[{") || !valueString.EndsWith("}]}"))
continue;
valueString = valueString.Replace("{\"Stops\":[{", "[{");
valueString = valueString.Replace("}]}", "}]");
propertyEntity["Value"] = valueString;
}
}
public int UserVersion => 11;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
foreach (BsonDocument bsonDocument in collection.FindAll())
{
foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray)
Migrate(bsonLayer);
foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray)
Migrate(bsonLayer);
collection.Update(bsonDocument);
}
}
}
}

View File

@ -10,7 +10,6 @@ using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Artemis.Core; using Artemis.Core;
using SkiaSharp;
using Stylet; using Stylet;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared
@ -88,6 +87,11 @@ namespace Artemis.UI.Shared
set => SetValue(HighlightedLedsProperty, value); set => SetValue(HighlightedLedsProperty, value);
} }
/// <summary>
/// Occurs when a LED of the device has been clicked
/// </summary>
public event EventHandler<LedClickedEventArgs>? LedClicked;
/// <inheritdoc /> /// <inheritdoc />
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)
{ {
@ -140,6 +144,27 @@ namespace Artemis.UI.Shared
return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height); return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height);
} }
/// <summary>
/// Invokes the <see cref="LedClicked" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnLedClicked(LedClickedEventArgs e)
{
LedClicked?.Invoke(this, e);
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing) _timer.Stop();
}
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight) private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
{ {
@ -189,8 +214,8 @@ namespace Artemis.UI.Shared
return; return;
Point position = e.GetPosition(this); Point position = e.GetPosition(this);
double x = (position.X / RenderSize.Width); double x = position.X / RenderSize.Width;
double y = (position.Y / RenderSize.Height); double y = position.Y / RenderSize.Height;
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height); Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.DisplayGeometry != null && l.LedRect.Contains(scaledPosition)); DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.DisplayGeometry != null && l.LedRect.Contains(scaledPosition));
@ -317,35 +342,6 @@ namespace Artemis.UI.Shared
drawingContext.Close(); drawingContext.Close();
} }
#region Events
public event EventHandler<LedClickedEventArgs>? LedClicked;
/// <summary>
/// Invokes the <see cref="LedClicked" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnLedClicked(LedClickedEventArgs e)
{
LedClicked?.Invoke(this, e);
}
#endregion
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing) _timer.Stop();
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
@ -353,7 +349,5 @@ namespace Artemis.UI.Shared
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#endregion
} }
} }

View File

@ -37,10 +37,7 @@
Background="{StaticResource Checkerboard}"> Background="{StaticResource Checkerboard}">
<Rectangle Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp"> <Rectangle Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp">
<Rectangle.Fill> <Rectangle.Fill>
<LinearGradientBrush <LinearGradientBrush x:Name="GradientPreview" EndPoint="1,0" StartPoint="0,0" />
GradientStops="{Binding ColorGradient.Stops, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorGradientToGradientStopsConverter}}"
EndPoint="1,0"
StartPoint="0,0" />
</Rectangle.Fill> </Rectangle.Fill>
</Rectangle> </Rectangle>
</Border> </Border>

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Properties; using Artemis.UI.Shared.Properties;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -17,12 +19,14 @@ namespace Artemis.UI.Shared
{ {
private static IColorPickerService? _colorPickerService; private static IColorPickerService? _colorPickerService;
private bool _inCallback; private bool _inCallback;
private ColorGradientToGradientStopsConverter _gradientConverter;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="GradientPicker" /> class /// Creates a new instance of the <see cref="GradientPicker" /> class
/// </summary> /// </summary>
public GradientPicker() public GradientPicker()
{ {
_gradientConverter = new ColorGradientToGradientStopsConverter();
InitializeComponent(); InitializeComponent();
} }
@ -99,10 +103,27 @@ namespace Artemis.UI.Shared
return; return;
gradientPicker._inCallback = true; gradientPicker._inCallback = true;
if (e.OldValue is ColorGradient oldGradient)
oldGradient.CollectionChanged -= gradientPicker.GradientChanged;
if (e.NewValue is ColorGradient newGradient)
newGradient.CollectionChanged += gradientPicker.GradientChanged;
gradientPicker.UpdateGradientStops();
gradientPicker.OnPropertyChanged(nameof(ColorGradient)); gradientPicker.OnPropertyChanged(nameof(ColorGradient));
gradientPicker._inCallback = false; gradientPicker._inCallback = false;
} }
private void GradientChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
Dispatcher.Invoke(UpdateGradientStops);
}
private void UpdateGradientStops()
{
GradientPreview.GradientStops = (GradientStopCollection)_gradientConverter.Convert(ColorGradient, null!, null!, null!);
}
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e) private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
{ {
if (_colorPickerService == null) if (_colorPickerService == null)

View File

@ -14,18 +14,18 @@ namespace Artemis.UI.Shared
/// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a /// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a
/// <see cref="T:System.Windows.Media.GradientStopCollection" />. /// <see cref="T:System.Windows.Media.GradientStopCollection" />.
/// </summary> /// </summary>
[ValueConversion(typeof(List<ColorGradientStop>), typeof(GradientStopCollection))] [ValueConversion(typeof(ColorGradient), typeof(GradientStopCollection))]
public class ColorGradientToGradientStopsConverter : IValueConverter public class ColorGradientToGradientStopsConverter : IValueConverter
{ {
/// <inheritdoc /> /// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
List<ColorGradientStop> colorGradients = (List<ColorGradientStop>) value; ColorGradient? colorGradient = value as ColorGradient;
GradientStopCollection collection = new(); GradientStopCollection collection = new();
if (colorGradients == null) if (colorGradient == null)
return collection; return collection;
foreach (ColorGradientStop c in colorGradients.OrderBy(s => s.Position)) foreach (ColorGradientStop c in colorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position)); collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
return collection; return collection;
} }
@ -33,8 +33,8 @@ namespace Artemis.UI.Shared
/// <inheritdoc /> /// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
GradientStopCollection collection = (GradientStopCollection) value; GradientStopCollection? collection = value as GradientStopCollection;
List<ColorGradientStop> colorGradients = new(); ColorGradient colorGradients = new();
if (collection == null) if (collection == null)
return colorGradients; return colorGradients;

View File

@ -20,7 +20,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
{ {
_gradientEditorViewModel = gradientEditorViewModel; _gradientEditorViewModel = gradientEditorViewModel;
ColorStop = colorStop; ColorStop = colorStop;
ColorStop.PropertyChanged += ColorStopOnPropertyChanged;
} }
public ColorGradientStop ColorStop { get; } public ColorGradientStop ColorStop { get; }
@ -59,11 +58,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
set => SetAndNotify(ref _willRemoveColorStop, value); set => SetAndNotify(ref _willRemoveColorStop, value);
} }
private void ColorStopOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
_gradientEditorViewModel.ColorGradient.OnColorValuesUpdated();
}
#region Movement #region Movement
public void StopMouseDown(object sender, MouseButtonEventArgs e) public void StopMouseDown(object sender, MouseButtonEventArgs e)
@ -102,7 +96,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
double minValue = 0.0; double minValue = 0.0;
double maxValue = _gradientEditorViewModel.PreviewWidth; double maxValue = _gradientEditorViewModel.PreviewWidth;
List<ColorGradientStop> stops = _gradientEditorViewModel.ColorGradient.Stops.OrderBy(s => s.Position).ToList(); List<ColorGradientStop> stops = _gradientEditorViewModel.ColorGradient.ToList();
ColorGradientStop? previous = stops.IndexOf(ColorStop) >= 1 ? stops[stops.IndexOf(ColorStop) - 1] : null; ColorGradientStop? previous = stops.IndexOf(ColorStop) >= 1 ? stops[stops.IndexOf(ColorStop) - 1] : null;
ColorGradientStop? next = stops.IndexOf(ColorStop) + 1 < stops.Count ? stops[stops.IndexOf(ColorStop) + 1] : null; ColorGradientStop? next = stops.IndexOf(ColorStop) + 1 < stops.Count ? stops[stops.IndexOf(ColorStop) + 1] : null;
if (previous != null) if (previous != null)
@ -111,7 +105,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
maxValue = next.Position * _gradientEditorViewModel.PreviewWidth; maxValue = next.Position * _gradientEditorViewModel.PreviewWidth;
Offset = Math.Max(minValue, Math.Min(maxValue, position.X)); Offset = Math.Max(minValue, Math.Min(maxValue, position.X));
_gradientEditorViewModel.ColorGradient.OnColorValuesUpdated();
} }
#endregion #endregion

View File

@ -66,7 +66,7 @@
<Rectangle x:Name="Preview" Height="40" shared:SizeObserver.Observe="True" shared:SizeObserver.ObservedWidth="{Binding PreviewWidth, Mode=OneWayToSource}"> <Rectangle x:Name="Preview" Height="40" shared:SizeObserver.Observe="True" shared:SizeObserver.ObservedWidth="{Binding PreviewWidth, Mode=OneWayToSource}">
<Rectangle.Fill> <Rectangle.Fill>
<LinearGradientBrush <LinearGradientBrush
GradientStops="{Binding ColorGradient.Stops, Converter={StaticResource ColorGradientToGradientStopsConverter}}" GradientStops="{Binding ColorGradient, Converter={StaticResource ColorGradientToGradientStopsConverter}}"
EndPoint="1,0" EndPoint="1,0"
StartPoint="0,0" /> StartPoint="0,0" />
</Rectangle.Fill> </Rectangle.Fill>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
@ -6,6 +7,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Stylet; using Stylet;
namespace Artemis.UI.Shared.Screens.GradientEditor namespace Artemis.UI.Shared.Screens.GradientEditor
@ -21,12 +23,24 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
ColorGradient = colorGradient; ColorGradient = colorGradient;
ColorStopViewModels = new BindableCollection<ColorStopViewModel>(); ColorStopViewModels = new BindableCollection<ColorStopViewModel>();
_originalStops = ColorGradient.Stops.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList(); _originalStops = ColorGradient.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList();
PropertyChanged += UpdateColorStopViewModels; PropertyChanged += UpdateColorStopViewModels;
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
} }
public BindableCollection<ColorStopViewModel> ColorStopViewModels { get; set; } #region Overrides of DialogViewModelBase
/// <inheritdoc />
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
{
ColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
base.OnDialogClosed(sender, e);
}
#endregion
public BindableCollection<ColorStopViewModel> ColorStopViewModels { get; }
public ColorStopViewModel? SelectedColorStopViewModel public ColorStopViewModel? SelectedColorStopViewModel
{ {
@ -48,15 +62,19 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
set => SetAndNotify(ref _previewWidth, value); set => SetAndNotify(ref _previewWidth, value);
} }
public ColorGradient Stops
{
get => ColorGradient;
}
public void AddColorStop(object sender, MouseEventArgs e) public void AddColorStop(object sender, MouseEventArgs e)
{ {
Canvas? child = VisualTreeUtilities.FindChild<Canvas>((DependencyObject) sender, null); Canvas? child = VisualTreeUtilities.FindChild<Canvas>((DependencyObject) sender, null);
float position = (float) (e.GetPosition(child).X / PreviewWidth); float position = (float) (e.GetPosition(child).X / PreviewWidth);
ColorGradientStop stop = new(ColorGradient.GetColor(position), position); ColorGradientStop stop = new(ColorGradient.GetColor(position), position);
ColorGradient.Stops.Add(stop); ColorGradient.Add(stop);
ColorGradient.OnColorValuesUpdated();
int index = ColorGradient.Stops.OrderBy(s => s.Position).ToList().IndexOf(stop); int index = ColorGradient.IndexOf(stop);
ColorStopViewModel viewModel = new(this, stop); ColorStopViewModel viewModel = new(this, stop);
ColorStopViewModels.Insert(index, viewModel); ColorStopViewModels.Insert(index, viewModel);
@ -69,8 +87,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
return; return;
ColorStopViewModels.Remove(colorStopViewModel); ColorStopViewModels.Remove(colorStopViewModel);
ColorGradient.Stops.Remove(colorStopViewModel.ColorStop); ColorGradient.Remove(colorStopViewModel.ColorStop);
ColorGradient.OnColorValuesUpdated();
SelectColorStop(null); SelectColorStop(null);
} }
@ -97,9 +114,9 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
public override void Cancel() public override void Cancel()
{ {
// Restore the saved state // Restore the saved state
ColorGradient.Stops.Clear(); ColorGradient.Clear();
ColorGradient.Stops.AddRange(_originalStops); foreach (ColorGradientStop colorGradientStop in _originalStops)
ColorGradient.OnColorValuesUpdated(); ColorGradient.Add(colorGradientStop);
base.Cancel(); base.Cancel();
} }
@ -107,8 +124,13 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
private void UpdateColorStopViewModels(object? sender, PropertyChangedEventArgs e) private void UpdateColorStopViewModels(object? sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName != nameof(PreviewWidth)) return; if (e.PropertyName != nameof(PreviewWidth)) return;
foreach (ColorGradientStop colorStop in ColorGradient.Stops.OrderBy(s => s.Position)) foreach (ColorGradientStop colorStop in ColorGradient)
ColorStopViewModels.Add(new ColorStopViewModel(this, colorStop)); ColorStopViewModels.Add(new ColorStopViewModel(this, colorStop));
} }
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
NotifyOfPropertyChange(nameof(ColorGradient));
}
} }
} }

View File

@ -19,7 +19,6 @@ namespace Artemis.UI.Shared.Services
{ {
internal class ProfileEditorService : IProfileEditorService internal class ProfileEditorService : IProfileEditorService
{ {
private readonly ICoreService _coreService;
private readonly IKernel _kernel; private readonly IKernel _kernel;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
@ -28,6 +27,7 @@ namespace Artemis.UI.Shared.Services
private readonly object _selectedProfileElementLock = new(); private readonly object _selectedProfileElementLock = new();
private readonly object _selectedProfileLock = new(); private readonly object _selectedProfileLock = new();
private TimeSpan _currentTime; private TimeSpan _currentTime;
private bool _doTick;
private int _pixelsPerSecond; private int _pixelsPerSecond;
public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService) public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService)
@ -35,16 +35,63 @@ namespace Artemis.UI.Shared.Services
_kernel = kernel; _kernel = kernel;
_logger = logger; _logger = logger;
_profileService = profileService; _profileService = profileService;
_coreService = coreService;
_rgbService = rgbService; _rgbService = rgbService;
_registeredPropertyEditors = new List<PropertyInputRegistration>(); _registeredPropertyEditors = new List<PropertyInputRegistration>();
coreService.FrameRendered += CoreServiceOnFrameRendered;
PixelsPerSecond = 100; PixelsPerSecond = 100;
} }
public event EventHandler? CurrentTimelineChanged;
protected virtual void OnSelectedProfileChanged(ProfileEventArgs e)
{
ProfileSelected?.Invoke(this, e);
}
protected virtual void OnSelectedProfileUpdated(ProfileEventArgs e)
{
SelectedProfileUpdated?.Invoke(this, e);
}
protected virtual void OnSelectedProfileElementChanged(RenderProfileElementEventArgs e)
{
ProfileElementSelected?.Invoke(this, e);
}
protected virtual void OnSelectedProfileElementUpdated(RenderProfileElementEventArgs e)
{
SelectedProfileElementUpdated?.Invoke(this, e);
}
protected virtual void OnCurrentTimeChanged()
{
CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnCurrentTimelineChanged()
{
CurrentTimelineChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnPixelsPerSecondChanged()
{
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnProfilePreviewUpdated()
{
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnSelectedDataBindingChanged()
{
SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty);
}
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{ {
_coreService.FrameRendered -= CoreServiceOnFrameRendered; if (!_doTick) return;
_doTick = false;
Execute.PostToUIThread(OnProfilePreviewUpdated); Execute.PostToUIThread(OnProfilePreviewUpdated);
} }
@ -72,6 +119,26 @@ namespace Artemis.UI.Shared.Services
UpdateProfilePreview(); UpdateProfilePreview();
} }
private void Tick()
{
if (SelectedProfile == null || _doTick)
return;
// Stick to the main segment for any element that is not currently selected
foreach (Folder folder in SelectedProfile.GetAllFolders())
folder.Timeline.Override(CurrentTime, folder.Timeline.PlayMode == TimelinePlayMode.Repeat);
foreach (Layer layer in SelectedProfile.GetAllLayers())
layer.Timeline.Override(CurrentTime, (layer != SelectedProfileElement || layer.Timeline.Length < CurrentTime) && layer.Timeline.PlayMode == TimelinePlayMode.Repeat);
_doTick = true;
}
private void SelectedProfileOnDeactivated(object? sender, EventArgs e)
{
// Execute.PostToUIThread(() => ChangeSelectedProfile(null));
ChangeSelectedProfile(null);
}
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
public bool Playing { get; set; } public bool Playing { get; set; }
@ -86,7 +153,7 @@ namespace Artemis.UI.Shared.Services
{ {
if (_currentTime.Equals(value)) return; if (_currentTime.Equals(value)) return;
_currentTime = value; _currentTime = value;
UpdateProfilePreview(); Tick();
OnCurrentTimeChanged(); OnCurrentTimeChanged();
} }
} }
@ -180,16 +247,9 @@ namespace Artemis.UI.Shared.Services
public void UpdateProfilePreview() public void UpdateProfilePreview()
{ {
if (SelectedProfile == null) if (Playing)
return; return;
Tick();
// Stick to the main segment for any element that is not currently selected
foreach (Folder folder in SelectedProfile.GetAllFolders())
folder.Timeline.Override(CurrentTime, folder.Timeline.PlayMode == TimelinePlayMode.Repeat);
foreach (Layer layer in SelectedProfile.GetAllLayers())
layer.Timeline.Override(CurrentTime, (layer != SelectedProfileElement || layer.Timeline.Length < CurrentTime) && layer.Timeline.PlayMode == TimelinePlayMode.Repeat);
_coreService.FrameRendered += CoreServiceOnFrameRendered;
} }
public bool UndoUpdateProfile() public bool UndoUpdateProfile()
@ -357,6 +417,15 @@ namespace Artemis.UI.Shared.Services
.ToList(); .ToList();
} }
public event EventHandler<ProfileEventArgs>? ProfileSelected;
public event EventHandler<ProfileEventArgs>? SelectedProfileUpdated;
public event EventHandler<RenderProfileElementEventArgs>? ProfileElementSelected;
public event EventHandler<RenderProfileElementEventArgs>? SelectedProfileElementUpdated;
public event EventHandler? SelectedDataBindingChanged;
public event EventHandler? CurrentTimeChanged;
public event EventHandler? PixelsPerSecondChanged;
public event EventHandler? ProfilePreviewUpdated;
#region Copy/paste #region Copy/paste
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement) public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
@ -436,70 +505,5 @@ namespace Artemis.UI.Shared.Services
} }
#endregion #endregion
#region Events
public event EventHandler<ProfileEventArgs>? ProfileSelected;
public event EventHandler<ProfileEventArgs>? SelectedProfileUpdated;
public event EventHandler<RenderProfileElementEventArgs>? ProfileElementSelected;
public event EventHandler<RenderProfileElementEventArgs>? SelectedProfileElementUpdated;
public event EventHandler? SelectedDataBindingChanged;
public event EventHandler? CurrentTimeChanged;
public event EventHandler? PixelsPerSecondChanged;
public event EventHandler? ProfilePreviewUpdated;
public event EventHandler? CurrentTimelineChanged;
protected virtual void OnSelectedProfileChanged(ProfileEventArgs e)
{
ProfileSelected?.Invoke(this, e);
}
protected virtual void OnSelectedProfileUpdated(ProfileEventArgs e)
{
SelectedProfileUpdated?.Invoke(this, e);
}
protected virtual void OnSelectedProfileElementChanged(RenderProfileElementEventArgs e)
{
ProfileElementSelected?.Invoke(this, e);
}
protected virtual void OnSelectedProfileElementUpdated(RenderProfileElementEventArgs e)
{
SelectedProfileElementUpdated?.Invoke(this, e);
}
protected virtual void OnCurrentTimeChanged()
{
CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnCurrentTimelineChanged()
{
CurrentTimelineChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnPixelsPerSecondChanged()
{
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnProfilePreviewUpdated()
{
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnSelectedDataBindingChanged()
{
SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty);
}
private void SelectedProfileOnDeactivated(object? sender, EventArgs e)
{
// Execute.PostToUIThread(() => ChangeSelectedProfile(null));
ChangeSelectedProfile(null);
}
#endregion
} }
} }

View File

@ -64,7 +64,7 @@ namespace Artemis.UI
string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt")); string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new(); using HttpClient client = new();
await client.PostAsync(url + "api/remote/bring-to-foreground", null!); await client.PostAsync(url + "remote/bring-to-foreground", null!);
} }
private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e) private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e)

View File

@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.ProfileEditor.Dialogs;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
@ -12,12 +14,15 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference> public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
{ {
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IDialogService _dialogService;
private BindableCollection<LayerBrushDescriptor> _descriptors; private BindableCollection<LayerBrushDescriptor> _descriptors;
public BrushPropertyInputViewModel(LayerProperty<LayerBrushReference> layerProperty, IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService) public BrushPropertyInputViewModel(LayerProperty<LayerBrushReference> layerProperty, IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService,
IDialogService dialogService)
: base(layerProperty, profileEditorService) : base(layerProperty, profileEditorService)
{ {
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_dialogService = dialogService;
UpdateEnumValues(); UpdateEnumValues();
} }
@ -43,7 +48,17 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
protected override void OnInputValueApplied() protected override void OnInputValueApplied()
{ {
if (LayerProperty.ProfileElement is Layer layer) if (LayerProperty.ProfileElement is Layer layer)
{
layer.ChangeLayerBrush(SelectedDescriptor); layer.ChangeLayerBrush(SelectedDescriptor);
if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any())
{
Execute.PostToUIThread(async () =>
{
await Task.Delay(400);
_dialogService.ShowDialogAt<LayerBrushPresetViewModel>("LayerProperties", new Dictionary<string, object> {{"layerBrush", layer.LayerBrush}});
});
}
}
} }
private void SetBrushByDescriptor(LayerBrushDescriptor value) private void SetBrushByDescriptor(LayerBrushDescriptor value)

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance propertyInput:ColorGradientPropertyInputViewModel}"> d:DataContext="{d:DesignInstance propertyInput:ColorGradientPropertyInputViewModel}">

View File

@ -134,14 +134,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
CreateRightSideInputViewModel(); CreateRightSideInputViewModel();
Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType(); Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType();
// Ensure the right static value is never null when the preferred type is a value type if (preferredType != null && RightSideInputViewModel.TargetType != preferredType)
if (preferredType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
RightSideInputViewModel.Value = preferredType.GetDefault();
else
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
if (RightSideInputViewModel.TargetType != preferredType)
RightSideInputViewModel.UpdateTargetType(preferredType); RightSideInputViewModel.UpdateTargetType(preferredType);
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
} }
} }
@ -294,6 +290,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
private void RightSideSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e) private void RightSideSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e)
{ {
DataModelConditionPredicate.PredicateType = ProfileRightSideType.Static; DataModelConditionPredicate.PredicateType = ProfileRightSideType.Static;
// Ensure the right static value is never null when the preferred type is a value type
Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType();
if (DataModelConditionPredicate.RightStaticValue == null && preferredType != null && preferredType.IsValueType)
DataModelConditionPredicate.UpdateRightSideStatic(preferredType.GetDefault());
Update(); Update();
} }

View File

@ -0,0 +1,42 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Dialogs.LayerBrushPresetView"
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:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="16">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
Select a brush preset
</TextBlock>
<ListBox ItemsSource="{Binding Presets}" SelectedItem="{Binding SelectedPreset}" HorizontalContentAlignment="Stretch" Margin="0 8 0 16">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type layerBrushes:ILayerBrushPreset}">
<Border x:Name="Border" Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<shared:ArtemisIcon Icon="{Binding Icon}" VerticalAlignment="Center" Width="30" Height="30"/>
<StackPanel Margin="8 0 0 0" Grid.Column="1">
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 8 0" Command="{s:Action Cancel}">
USE DEFAULT SETTINGS
</Button>
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,43 @@
using System.Threading.Tasks;
using Artemis.Core.LayerBrushes;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
{
public class LayerBrushPresetViewModel : DialogViewModelBase
{
private readonly BaseLayerBrush _layerBrush;
private ILayerBrushPreset _selectedPreset;
public LayerBrushPresetViewModel(BaseLayerBrush layerBrush)
{
_layerBrush = layerBrush;
Presets = new BindableCollection<ILayerBrushPreset>();
Presets.AddRange(layerBrush.Presets);
}
public BindableCollection<ILayerBrushPreset> Presets { get; }
public ILayerBrushPreset SelectedPreset
{
get => _selectedPreset;
set
{
SetAndNotify(ref _selectedPreset, value);
SelectPreset(value);
}
}
public void SelectPreset(ILayerBrushPreset preset)
{
_layerBrush.BaseProperties?.ResetAllLayerProperties();
preset.Apply();
Execute.OnUIThreadAsync(async () =>
{
await Task.Delay(250);
Session?.Close(true);
});
}
}
}

View File

@ -140,12 +140,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
if (DynamicSelectionViewModel != null) if (DynamicSelectionViewModel != null)
DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath); DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath);
else if (StaticInputViewModel != null) else if (StaticInputViewModel != null)
{
// Ensure the right static value is never null when the preferred type is a value type
StaticInputViewModel.Value = Modifier.ParameterStaticValue; StaticInputViewModel.Value = Modifier.ParameterStaticValue;
if (SelectedModifierType.ParameterType.IsValueType && StaticInputViewModel.Value == null)
StaticInputViewModel.Value = SelectedModifierType.ParameterType.GetDefault();
}
} }
private void ExecuteSelectModifierTypeCommand(object context) private void ExecuteSelectModifierTypeCommand(object context)
@ -197,6 +192,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
private void DynamicSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e) private void DynamicSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e)
{ {
Modifier.ParameterType = ProfileRightSideType.Static; Modifier.ParameterType = ProfileRightSideType.Static;
// Ensure the right static value is never null when the preferred type is a value type
if (SelectedModifierType.ParameterType != null &&
SelectedModifierType.ParameterType.IsValueType && Modifier.ParameterStaticValue == null)
Modifier.UpdateParameterStatic(SelectedModifierType.ParameterType.GetDefault());
Update(); Update();
} }

View File

@ -66,6 +66,7 @@
<KeyBinding Command="{s:Action Play}" Key="Space" /> <KeyBinding Command="{s:Action Play}" Key="Space" />
<KeyBinding Command="{s:Action PlayFromStart}" Modifiers="Shift" Key="Space" /> <KeyBinding Command="{s:Action PlayFromStart}" Modifiers="Shift" Key="Space" />
</UserControl.InputBindings> </UserControl.InputBindings>
<materialDesign:DialogHost Identifier="LayerProperties" DialogTheme="Inherit">
<Grid x:Name="ContainerGrid"> <Grid x:Name="ContainerGrid">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -255,7 +256,7 @@
X1="0" X1="0"
X2="0" X2="0"
Y1="0" Y1="0"
Y2="{Binding ActualHeight, ElementName=ContainerGrid}" Y2="{Binding ActualHeight, ElementName=TimelineHeaderScrollViewer}"
StrokeThickness="2" StrokeThickness="2"
Stroke="{StaticResource SecondaryAccentBrush}" /> Stroke="{StaticResource SecondaryAccentBrush}" />
</Canvas> </Canvas>
@ -268,7 +269,7 @@
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
ScrollChanged="TimelineScrollChanged"> ScrollChanged="TimelineScrollChanged">
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"> <Grid Background="{DynamicResource MaterialDesignToolBarBackground}" x:Name="RailsGrid">
<Canvas Grid.Column="0" Panel.ZIndex="1"> <Canvas Grid.Column="0" Panel.ZIndex="1">
<Line Canvas.Left="{Binding TimeCaretPosition}" <Line Canvas.Left="{Binding TimeCaretPosition}"
Cursor="SizeWE" Cursor="SizeWE"
@ -278,7 +279,7 @@
X1="0" X1="0"
X2="0" X2="0"
Y1="0" Y1="0"
Y2="{Binding ActualHeight, ElementName=ContainerGrid}" Y2="{Binding ActualHeight, ElementName=RailsGrid}"
StrokeThickness="2" StrokeThickness="2"
Stroke="{StaticResource SecondaryAccentBrush}" /> Stroke="{StaticResource SecondaryAccentBrush}" />
</Canvas> </Canvas>
@ -443,4 +444,5 @@
</Grid> </Grid>
</Grid> </Grid>
</materialDesign:DialogHost>
</UserControl> </UserControl>

View File

@ -32,7 +32,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
private LayerBrushSettingsWindowViewModel? _layerBrushSettingsWindowVm; private LayerBrushSettingsWindowViewModel _layerBrushSettingsWindowVm;
private LayerEffectSettingsWindowViewModel _layerEffectSettingsWindowVm; private LayerEffectSettingsWindowViewModel _layerEffectSettingsWindowVm;
public TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService, IDialogService dialogService, IWindowManager windowManager) public TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService, IDialogService dialogService, IWindowManager windowManager)

View File

@ -47,7 +47,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
public void ResetToDefault() public void ResetToDefault()
{ {
LayerProperty.ApplyDefaultValue(_profileEditorService.CurrentTime); LayerProperty.ApplyDefaultValue();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }

View File

@ -321,6 +321,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta);
// TODO: Only update when there are data bindings // TODO: Only update when there are data bindings
if (!_profileEditorService.Playing)
_profileEditorService.UpdateProfilePreview(); _profileEditorService.UpdateProfilePreview();
} }

View File

@ -105,8 +105,8 @@
</VisualBrush> </VisualBrush>
</Grid.Background> </Grid.Background>
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
<shared:DeviceVisualizer Device="{Binding Device}" <shared:DeviceVisualizer Device="{Binding Device}"
HighlightedLeds="{Binding SelectedLeds}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
ShowColors="True" ShowColors="True"

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Input;
using System.Xml.Serialization; using System.Xml.Serialization;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
@ -13,20 +13,28 @@ using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
using Ookii.Dialogs.Wpf; using Ookii.Dialogs.Wpf;
using RGB.NET.Layout; using RGB.NET.Layout;
using SkiaSharp;
using Stylet; using Stylet;
namespace Artemis.UI.Screens.Settings.Device namespace Artemis.UI.Screens.Settings.Device
{ {
public class DeviceDialogViewModel : Conductor<Screen>.Collection.OneActive public class DeviceDialogViewModel : Conductor<Screen>.Collection.OneActive
{ {
private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private ArtemisLed _selectedLed;
private SnackbarMessageQueue _deviceMessageQueue; private SnackbarMessageQueue _deviceMessageQueue;
private BindableCollection<ArtemisLed> _selectedLeds;
public DeviceDialogViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) public DeviceDialogViewModel(ArtemisDevice device,
ICoreService coreService,
IDeviceService deviceService,
IRgbService rgbService,
IDialogService dialogService,
IDeviceDebugVmFactory factory)
{ {
_coreService = coreService;
_deviceService = deviceService; _deviceService = deviceService;
_rgbService = rgbService; _rgbService = rgbService;
_dialogService = dialogService; _dialogService = dialogService;
@ -39,19 +47,8 @@ namespace Artemis.UI.Screens.Settings.Device
Items.Add(factory.DeviceLedsTabViewModel(device)); Items.Add(factory.DeviceLedsTabViewModel(device));
ActiveItem = Items.First(); ActiveItem = Items.First();
DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis"; DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis";
}
protected override void OnInitialActivate() SelectedLeds = new BindableCollection<ArtemisLed>();
{
DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
Device.DeviceUpdated += DeviceOnDeviceUpdated;
base.OnInitialActivate();
}
protected override void OnClose()
{
Device.DeviceUpdated -= DeviceOnDeviceUpdated;
base.OnClose();
} }
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
@ -63,29 +60,61 @@ namespace Artemis.UI.Screens.Settings.Device
set => SetAndNotify(ref _deviceMessageQueue, value); set => SetAndNotify(ref _deviceMessageQueue, value);
} }
public ArtemisLed SelectedLed
{
get => _selectedLed;
set
{
if (!SetAndNotify(ref _selectedLed, value)) return;
NotifyOfPropertyChange(nameof(SelectedLeds));
}
}
public bool CanExportLayout => Device.Layout?.IsValid ?? false; public bool CanExportLayout => Device.Layout?.IsValid ?? false;
public List<ArtemisLed> SelectedLeds => SelectedLed != null ? new List<ArtemisLed> {SelectedLed} : null; public BindableCollection<ArtemisLed> SelectedLeds
{
get => _selectedLeds;
set => SetAndNotify(ref _selectedLeds, value);
}
public bool CanOpenImageDirectory => Device.Layout?.Image != null; public bool CanOpenImageDirectory => Device.Layout?.Image != null;
public void OnLedClicked(object sender, LedClickedEventArgs e)
{
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
SelectedLeds.Clear();
SelectedLeds.Add(e.Led);
}
protected override void OnInitialActivate()
{
_coreService.FrameRendering += CoreServiceOnFrameRendering;
DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
Device.DeviceUpdated += DeviceOnDeviceUpdated;
base.OnInitialActivate();
}
protected override void OnClose()
{
_coreService.FrameRendering -= CoreServiceOnFrameRendering;
Device.DeviceUpdated -= DeviceOnDeviceUpdated;
base.OnClose();
}
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
{
if (SelectedLeds == null || !SelectedLeds.Any())
return;
using SKPaint highlightPaint = new() {Color = SKColors.White};
using SKPaint dimPaint = new() {Color = new SKColor(0, 0, 0, 192)};
foreach (ArtemisLed artemisLed in Device.Leds)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, SelectedLeds.Contains(artemisLed) ? highlightPaint : dimPaint);
}
private void DeviceOnDeviceUpdated(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CanExportLayout));
}
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
#region Command handlers #region Command handlers
public void ClearSelection() public void ClearSelection()
{ {
SelectedLed = null; SelectedLeds.Clear();
} }
public void IdentifyDevice() public void IdentifyDevice()
@ -190,19 +219,5 @@ namespace Artemis.UI.Screens.Settings.Device
#endregion #endregion
// ReSharper restore UnusedMember.Global // ReSharper restore UnusedMember.Global
#region Event handlers
private void DeviceOnDeviceUpdated(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CanExportLayout));
}
public void OnLedClicked(object sender, LedClickedEventArgs e)
{
SelectedLed = e.Led;
}
#endregion
} }
} }

View File

@ -4,34 +4,39 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs" 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" xmlns:converters="clr-namespace:Artemis.UI.Converters"
x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}"> d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}">
<UserControl.Resources> <UserControl.Resources>
<Converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/> <converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<DataGrid ItemsSource="{Binding Device.Leds}" <DataGrid ItemsSource="{Binding LedViewModels}"
d:DataContext="{d:DesignInstance Type={x:Type core:ArtemisLed}}" d:DataContext="{d:DesignInstance Type={x:Type tabs:DeviceLedsTabLedViewModel}}"
CanUserSortColumns="True" CanUserSortColumns="True"
IsReadOnly="True" IsReadOnly="True"
CanUserAddRows="False" CanUserAddRows="False"
AutoGenerateColumns="False" AutoGenerateColumns="False"
materialDesign:DataGridAssist.CellPadding="5" materialDesign:DataGridAssist.CellPadding="5"
materialDesign:DataGridAssist.ColumnHeaderPadding="5" materialDesign:DataGridAssist.ColumnHeaderPadding="5"
SelectedItem="{Binding Parent.SelectedLed}"
CanUserResizeRows="False" CanUserResizeRows="False"
VirtualizingStackPanel.VirtualizationMode="Standard"
Margin="10"> Margin="10">
<DataGrid.Resources>
<Style TargetType="DataGridRow" BasedOn="{StaticResource MaterialDesignDataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns> <DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Id}" Header="LED ID" Width="Auto" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Color}" Header="Color (ARGB)" Width="Auto" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.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 ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Shape}" Header="Shape" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Shape}" Header="Shape" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Size}" Header="Size" Width="Auto" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Size}" Header="Size" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.CustomData}" Header="LED data" Width="Auto" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.CustomData}" Header="LED data" Width="Auto" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>

View File

@ -1,17 +1,92 @@
using Artemis.Core; using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Stylet; using Stylet;
namespace Artemis.UI.Screens.Settings.Device.Tabs namespace Artemis.UI.Screens.Settings.Device.Tabs
{ {
public class DeviceLedsTabViewModel : Screen public class DeviceLedsTabViewModel : Screen
{ {
public DeviceLedsTabViewModel(ArtemisDevice device) public DeviceLedsTabViewModel(ArtemisDevice device)
{ {
Device = device; Device = device;
DisplayName = "LEDS"; DisplayName = "LEDS";
LedViewModels = new BindableCollection<DeviceLedsTabLedViewModel>();
} }
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public BindableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateSelectedLeds();
}
private void UpdateSelectedLeds()
{
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
deviceLedsTabLedViewModel.Update();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
BindableCollection<ArtemisLed> selectedLeds = ((DeviceDialogViewModel) Parent).SelectedLeds;
LedViewModels.Clear();
LedViewModels.AddRange(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, selectedLeds)));
selectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
base.OnClose();
}
#endregion
}
public class DeviceLedsTabLedViewModel : PropertyChangedBase
{
private readonly BindableCollection<ArtemisLed> _selectedLeds;
private bool _isSelected;
public DeviceLedsTabLedViewModel(ArtemisLed artemisLed, BindableCollection<ArtemisLed> selectedLeds)
{
_selectedLeds = selectedLeds;
ArtemisLed = artemisLed;
Update();
}
public ArtemisLed ArtemisLed { get; }
public bool IsSelected
{
get => _isSelected;
set
{
if (!SetAndNotify(ref _isSelected, value)) return;
Apply();
}
}
public void Update()
{
IsSelected = _selectedLeds.Contains(ArtemisLed);
}
public void Apply()
{
if (IsSelected && !_selectedLeds.Contains(ArtemisLed))
_selectedLeds.Add(ArtemisLed);
else if (!IsSelected && _selectedLeds.Contains(ArtemisLed))
_selectedLeds.Remove(ArtemisLed);
}
} }
} }

View File

@ -16,7 +16,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private bool _enabling; private bool _enabling;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private bool _canToggleEnabled;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield, bool showShield,

View File

@ -7,6 +7,7 @@
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
Title="Plugin configuration" Title="Plugin configuration"
Background="{DynamicResource MaterialDesignPaper}" Background="{DynamicResource MaterialDesignPaper}"
@ -25,7 +26,7 @@
<DockPanel> <DockPanel>
<controls:AppBar Type="Dense" Title="{Binding ActiveItem.Plugin.Info.Name}" DockPanel.Dock="Top" Margin="-18 0 0 0" ShowShadow="False"> <controls:AppBar Type="Dense" Title="{Binding ActiveItem.Plugin.Info.Name}" DockPanel.Dock="Top" Margin="-18 0 0 0" ShowShadow="False">
<controls:AppBar.AppIcon> <controls:AppBar.AppIcon>
<materialDesign:PackIcon Kind="{Binding Icon}" Width="20" Height="28" /> <shared:ArtemisIcon Icon="{Binding Icon}"></shared:ArtemisIcon>
</controls:AppBar.AppIcon> </controls:AppBar.AppIcon>
</controls:AppBar> </controls:AppBar>

View File

@ -25,5 +25,23 @@ namespace Artemis.UI.Screens.StartupWizard.Steps
StartupWizardViewModel startupWizardViewModel = (StartupWizardViewModel) Parent; StartupWizardViewModel startupWizardViewModel = (StartupWizardViewModel) Parent;
startupWizardViewModel.Continue(); startupWizardViewModel.Continue();
} }
#region Overrides of Screen
/// <inheritdoc />
protected override void OnActivate()
{
_rgbService.IsRenderPaused = true;
base.OnActivate();
}
/// <inheritdoc />
protected override void OnDeactivate()
{
_rgbService.IsRenderPaused = false;
base.OnDeactivate();
}
#endregion
} }
} }

View File

@ -106,7 +106,7 @@ namespace Artemis.UI.Services
public void RegisterControllers() public void RegisterControllers()
{ {
_webServerService.AddController<RemoteController>(); _webServerService.AddController<RemoteController>(Constants.CorePlugin.Features.First().Instance!);
} }
/// <inheritdoc /> /// <inheritdoc />