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:
commit
0e76ec31a3
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
@ -23,17 +24,16 @@ namespace Artemis.Core
|
||||
if (CurrentValue == null)
|
||||
return;
|
||||
|
||||
for (int index = 0; index < CurrentValue.Stops.Count; index++)
|
||||
for (int index = 0; index < CurrentValue.Count; index++)
|
||||
{
|
||||
int stopIndex = index;
|
||||
|
||||
void Setter(SKColor value)
|
||||
{
|
||||
CurrentValue.Stops[stopIndex].Color = value;
|
||||
CurrentValue.OnColorValuesUpdated();
|
||||
CurrentValue[stopIndex].Color = value;
|
||||
}
|
||||
|
||||
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 != null)
|
||||
_subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient = BaseValue;
|
||||
_subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient.CollectionChanged += SubscribedGradientOnPropertyChanged;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -8,7 +11,7 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// A gradient containing a list of <see cref="ColorGradientStop" />s
|
||||
/// </summary>
|
||||
public class ColorGradient : CorePropertyChanged
|
||||
public class ColorGradient : IList<ColorGradientStop>, INotifyCollectionChanged
|
||||
{
|
||||
private static readonly SKColor[] FastLedRainbow =
|
||||
{
|
||||
@ -23,19 +26,16 @@ namespace Artemis.Core
|
||||
new(0xFFFF0000) // and back to Red
|
||||
};
|
||||
|
||||
private readonly List<ColorGradientStop> _stops;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets all the colors in the color gradient
|
||||
/// </summary>
|
||||
@ -49,13 +49,16 @@ namespace Artemis.Core
|
||||
{
|
||||
List<SKColor> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
result = Stops.Select(c => c.Color).ToList();
|
||||
{
|
||||
result = this.Select(c => c.Color).ToList();
|
||||
}
|
||||
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++)
|
||||
result.AddRange(colors);
|
||||
}
|
||||
|
||||
if (seamless && !IsSeamless())
|
||||
result.Add(result[0]);
|
||||
|
||||
@ -77,11 +80,13 @@ namespace Artemis.Core
|
||||
{
|
||||
List<float> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
result = Stops.Select(c => c.Position).ToList();
|
||||
{
|
||||
result = this.Select(c => c.Position).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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 (int i = 0; i <= timesToRepeat; i++)
|
||||
@ -104,25 +109,16 @@ namespace Artemis.Core
|
||||
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>
|
||||
/// Gets a color at any position between 0.0 and 1.0 using interpolation
|
||||
/// </summary>
|
||||
/// <param name="position">A position between 0.0 and 1.0</param>
|
||||
public SKColor GetColor(float position)
|
||||
{
|
||||
if (!Stops.Any())
|
||||
if (!this.Any())
|
||||
return SKColor.Empty;
|
||||
|
||||
ColorGradientStop[] stops = Stops.ToArray();
|
||||
ColorGradientStop[] stops = this.ToArray();
|
||||
if (position <= 0) return stops[0].Color;
|
||||
if (position >= 1) return stops[^1].Color;
|
||||
ColorGradientStop left = stops[0];
|
||||
@ -160,7 +156,7 @@ namespace Artemis.Core
|
||||
{
|
||||
SKColor skColor = FastLedRainbow[index];
|
||||
float position = 1f / (FastLedRainbow.Length - 1f) * index;
|
||||
gradient.Stops.Add(new ColorGradientStop(skColor, position));
|
||||
gradient.Add(new ColorGradientStop(skColor, position));
|
||||
}
|
||||
|
||||
return gradient;
|
||||
@ -172,7 +168,141 @@ namespace Artemis.Core
|
||||
/// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns>
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -297,6 +297,9 @@ namespace Artemis.Core
|
||||
|
||||
private void ApplyTimeline(Timeline timeline)
|
||||
{
|
||||
if (timeline.Delta == TimeSpan.Zero)
|
||||
return;
|
||||
|
||||
General.Update(timeline);
|
||||
Transform.Update(timeline);
|
||||
if (LayerBrush != null)
|
||||
|
||||
@ -33,6 +33,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
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>
|
||||
/// Initializes the layer property
|
||||
/// <para>
|
||||
@ -54,6 +59,11 @@ namespace Artemis.Core
|
||||
/// <returns>If succeeded the resulting keyframe, otherwise <see langword="null" /></returns>
|
||||
ILayerPropertyKeyframe? AddKeyframeEntity(KeyframeEntity keyframeEntity);
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the property value with the default value
|
||||
/// </summary>
|
||||
void ApplyDefaultValue();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the layer properties internal state
|
||||
/// </summary>
|
||||
|
||||
@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -39,6 +38,107 @@ namespace Artemis.Core
|
||||
_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 />
|
||||
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
|
||||
|
||||
@ -62,28 +162,6 @@ namespace Artemis.Core
|
||||
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 />
|
||||
public void Dispose()
|
||||
{
|
||||
@ -91,7 +169,38 @@ namespace Artemis.Core
|
||||
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
|
||||
|
||||
@ -195,22 +304,25 @@ namespace Artemis.Core
|
||||
ReapplyUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the property value with the default value
|
||||
/// </summary>
|
||||
public void ApplyDefaultValue(TimeSpan? time)
|
||||
/// <inheritdoc />
|
||||
public void ApplyDefaultValue()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
string json = CoreJson.SerializeObject(DefaultValue, true);
|
||||
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!, time);
|
||||
KeyframesEnabled = false;
|
||||
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!, null);
|
||||
}
|
||||
|
||||
private void ReapplyUpdate()
|
||||
{
|
||||
ProfileElement.Timeline.ClearDelta();
|
||||
Update(ProfileElement.Timeline);
|
||||
// Create a timeline with the same position but a delta of zero
|
||||
Timeline temporaryTimeline = new();
|
||||
temporaryTimeline.Override(ProfileElement.Timeline.Position, false);
|
||||
temporaryTimeline.ClearDelta();
|
||||
|
||||
Update(temporaryTimeline);
|
||||
OnCurrentValueSet();
|
||||
}
|
||||
|
||||
@ -440,10 +552,10 @@ namespace Artemis.Core
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
|
||||
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
|
||||
dataBindingRegistration.ClearDataBinding();
|
||||
_dataBindingRegistrations.Clear();
|
||||
|
||||
|
||||
OnDataBindingPropertiesCleared();
|
||||
}
|
||||
|
||||
@ -593,7 +705,7 @@ namespace Artemis.Core
|
||||
throw new ArtemisCoreException("Layer property is not yet initialized");
|
||||
|
||||
if (!IsLoadedFromStorage)
|
||||
ApplyDefaultValue(null);
|
||||
ApplyDefaultValue();
|
||||
else
|
||||
try
|
||||
{
|
||||
@ -657,123 +769,5 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
@ -121,6 +121,31 @@ namespace Artemis.Core
|
||||
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>
|
||||
/// Called before property group is activated to allow you to populate <see cref="LayerProperty{T}.DefaultValue" /> on
|
||||
/// the properties you want
|
||||
@ -145,6 +170,27 @@ namespace Artemis.Core
|
||||
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)
|
||||
{
|
||||
if (path == null) throw new ArgumentNullException(nameof(path));
|
||||
@ -209,6 +255,17 @@ namespace Artemis.Core
|
||||
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)
|
||||
{
|
||||
string path = $"{Path}.{propertyInfo.Name}";
|
||||
@ -266,67 +323,11 @@ namespace Artemis.Core
|
||||
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 />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
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)
|
||||
layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
|
||||
|
||||
|
||||
@ -51,6 +51,18 @@ namespace Artemis.Core.DeviceProviders
|
||||
/// </summary>
|
||||
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>
|
||||
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
|
||||
/// </summary>
|
||||
|
||||
28
src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushPreset.cs
Normal file
28
src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushPreset.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.LayerBrushes
|
||||
@ -70,6 +72,16 @@ namespace Artemis.Core.LayerBrushes
|
||||
/// </summary>
|
||||
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>
|
||||
/// 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>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core.LayerBrushes
|
||||
{
|
||||
|
||||
@ -292,7 +292,7 @@
|
||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||
"FeatureId": null,
|
||||
"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,
|
||||
"KeyframeEntities": {
|
||||
"$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",
|
||||
"FeatureId": "Artemis.Plugins.LayerBrushes.Noise.NoiseBrushProvider-61cbbf01",
|
||||
"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,
|
||||
"KeyframeEntities": {
|
||||
"$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",
|
||||
"FeatureId": null,
|
||||
"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,
|
||||
"KeyframeEntities": {
|
||||
"$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",
|
||||
"FeatureId": "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
|
||||
"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,
|
||||
"KeyframeEntities": {
|
||||
"$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",
|
||||
"FeatureId": null,
|
||||
"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,
|
||||
"KeyframeEntities": {
|
||||
"$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",
|
||||
"FeatureId": "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
|
||||
"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,
|
||||
"KeyframeEntities": {
|
||||
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||
|
||||
@ -251,20 +251,16 @@ namespace Artemis.Core.Services
|
||||
// Draw a white overlay over the device
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -90,9 +90,7 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
/// <param name="device"></param>
|
||||
/// <param name="layout"></param>
|
||||
/// <param name="createMissingLeds"></param>
|
||||
/// <param name="removeExessiveLeds"></param>
|
||||
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds);
|
||||
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET
|
||||
|
||||
@ -312,7 +312,7 @@ namespace Artemis.Core.Services
|
||||
layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured);
|
||||
if (layout.IsValid)
|
||||
{
|
||||
ApplyDeviceLayout(device, layout, true, true);
|
||||
ApplyDeviceLayout(device, layout);
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
@ -321,7 +321,7 @@ namespace Artemis.Core.Services
|
||||
layout = device.DeviceProvider.LoadUserLayout(device);
|
||||
if (layout.IsValid)
|
||||
{
|
||||
ApplyDeviceLayout(device, layout, true, true);
|
||||
ApplyDeviceLayout(device, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@ -329,13 +329,13 @@ namespace Artemis.Core.Services
|
||||
layout = device.DeviceProvider.LoadLayout(device);
|
||||
if (layout.IsValid)
|
||||
{
|
||||
ApplyDeviceLayout(device, layout, true, true);
|
||||
ApplyDeviceLayout(device, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
// Finally fall back to a default layout
|
||||
layout = LoadDefaultLayout(device);
|
||||
ApplyDeviceLayout(device, layout, true, true);
|
||||
ApplyDeviceLayout(device, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@ -344,9 +344,9 @@ namespace Artemis.Core.Services
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,8 @@ namespace Artemis.Core.Services
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="profileModule" />.
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided
|
||||
/// <paramref name="profileModule" />.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
@ -64,7 +65,8 @@ namespace Artemis.Core.Services
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="dataModelExpansion" />.
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided
|
||||
/// <paramref name="dataModelExpansion" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
||||
/// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
|
||||
@ -114,7 +116,7 @@ namespace Artemis.Core.Services
|
||||
/// Adds a new Web API controller and restarts the web server
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// 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>
|
||||
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>
|
||||
/// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules.
|
||||
/// </summary>
|
||||
|
||||
@ -48,13 +48,13 @@ namespace Artemis.Core.Services
|
||||
protected override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (context.Route.SubPath == null)
|
||||
throw HttpException.NotFound();
|
||||
return;
|
||||
|
||||
// Split the sub path
|
||||
string[] pathParts = context.Route.SubPath.Substring(1).Split('/');
|
||||
// Expect a plugin ID and an endpoint
|
||||
if (pathParts == null || pathParts.Length != 2)
|
||||
throw HttpException.BadRequest("Path must contain a plugin ID and endpoint and nothing else.");
|
||||
if (pathParts.Length != 2)
|
||||
return;
|
||||
|
||||
// Find a matching plugin
|
||||
if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints))
|
||||
@ -78,7 +78,7 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => true;
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
internal string? ServerUrl { get; set; }
|
||||
|
||||
|
||||
@ -6,9 +6,9 @@ namespace Artemis.Core.Services
|
||||
{
|
||||
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; }
|
||||
@ -17,12 +17,14 @@ namespace Artemis.Core.Services
|
||||
|
||||
internal abstract class WebApiControllerRegistration
|
||||
{
|
||||
protected WebApiControllerRegistration(Type controllerType)
|
||||
protected WebApiControllerRegistration(PluginFeature feature, Type controllerType)
|
||||
{
|
||||
Feature = feature;
|
||||
ControllerType = controllerType;
|
||||
}
|
||||
|
||||
public abstract object UntypedFactory { get; }
|
||||
public Type ControllerType { get; set; }
|
||||
public PluginFeature Feature { get; }
|
||||
}
|
||||
}
|
||||
20
src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs
Normal file
20
src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
@ -8,7 +9,6 @@ using Artemis.Core.Modules;
|
||||
using EmbedIO;
|
||||
using EmbedIO.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using Ninject;
|
||||
using Serilog;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
@ -16,18 +16,19 @@ namespace Artemis.Core.Services
|
||||
internal class WebServerService : IWebServerService, IDisposable
|
||||
{
|
||||
private readonly List<WebApiControllerRegistration> _controllers;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly List<WebModuleRegistration> _modules;
|
||||
private readonly ILogger _logger;
|
||||
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;
|
||||
_controllers = new List<WebApiControllerRegistration>();
|
||||
_modules = new List<WebModuleRegistration>();
|
||||
|
||||
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
|
||||
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
|
||||
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
|
||||
|
||||
PluginsModule = new PluginsModule("/plugins");
|
||||
StartWebServer();
|
||||
@ -43,12 +44,18 @@ namespace Artemis.Core.Services
|
||||
Server?.Dispose();
|
||||
Server = null;
|
||||
|
||||
WebApiModule apiModule = new("/api/", JsonNetSerializer);
|
||||
WebApiModule apiModule = new("/", JsonNetSerializer);
|
||||
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
|
||||
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
|
||||
.WithLocalSessionManager()
|
||||
.WithModule(PluginsModule);
|
||||
|
||||
// Add registered modules
|
||||
foreach (var webModule in _modules)
|
||||
server = server.WithModule(webModule.CreateInstance());
|
||||
|
||||
server = server
|
||||
.WithModule(apiModule)
|
||||
.WithModule(PluginsModule)
|
||||
.HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception))
|
||||
.HandleUnhandledException(JsonExceptionHandlerCallback);
|
||||
|
||||
@ -166,9 +173,9 @@ namespace Artemis.Core.Services
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
@ -180,6 +187,26 @@ namespace Artemis.Core.Services
|
||||
|
||||
#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
|
||||
|
||||
private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception)
|
||||
@ -235,6 +262,25 @@ namespace Artemis.Core.Services
|
||||
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
|
||||
|
||||
#region IDisposable
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
@ -3,7 +3,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M1AttributeBasedPropertiesMigration : IStorageMigration
|
||||
public class M0001AttributeBasedPropertiesMigration : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 1;
|
||||
|
||||
@ -5,7 +5,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M2ProfileEntitiesEnabledMigration : IStorageMigration
|
||||
public class M0002ProfileEntitiesEnabledMigration : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 2;
|
||||
|
||||
@ -3,7 +3,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M3PluginEntitiesIndexChangesMigration : IStorageMigration
|
||||
public class M0003PluginEntitiesIndexChangesMigration : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 3;
|
||||
|
||||
@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M4ProfileSegmentsMigration : IStorageMigration
|
||||
public class M0004ProfileSegmentsMigration : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 4;
|
||||
|
||||
@ -3,7 +3,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M5DataBindingTypes : IStorageMigration
|
||||
public class M0005DataBindingTypes : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 5;
|
||||
|
||||
@ -4,7 +4,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M6PredicateAbstraction : IStorageMigration
|
||||
public class M0006PredicateAbstraction : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 6;
|
||||
|
||||
@ -5,7 +5,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M8PluginFeatures : IStorageMigration
|
||||
public class M0008PluginFeatures : IStorageMigration
|
||||
{
|
||||
private void Migrate(BsonValue bsonValue, Dictionary<string, string> pluginMap)
|
||||
{
|
||||
@ -3,7 +3,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M9DeviceCalibration : IStorageMigration
|
||||
public class M0009DeviceCalibration : IStorageMigration
|
||||
{
|
||||
public int UserVersion => 9;
|
||||
|
||||
@ -3,7 +3,7 @@ using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Migrations
|
||||
{
|
||||
public class M10BetterDataBindings : IStorageMigration
|
||||
public class M0010BetterDataBindings : IStorageMigration
|
||||
{
|
||||
private void Migrate(BsonValue bsonValue)
|
||||
{
|
||||
42
src/Artemis.Storage/Migrations/M0011ColorGradients.cs
Normal file
42
src/Artemis.Storage/Migrations/M0011ColorGradients.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,6 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Artemis.Core;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
@ -88,6 +87,11 @@ namespace Artemis.UI.Shared
|
||||
set => SetValue(HighlightedLedsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a LED of the device has been clicked
|
||||
/// </summary>
|
||||
public event EventHandler<LedClickedEventArgs>? LedClicked;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
@ -140,6 +144,27 @@ namespace Artemis.UI.Shared
|
||||
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)
|
||||
{
|
||||
@ -189,8 +214,8 @@ namespace Artemis.UI.Shared
|
||||
return;
|
||||
|
||||
Point position = e.GetPosition(this);
|
||||
double x = (position.X / RenderSize.Width);
|
||||
double y = (position.Y / RenderSize.Height);
|
||||
double x = position.X / RenderSize.Width;
|
||||
double y = position.Y / RenderSize.Height;
|
||||
|
||||
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
|
||||
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.DisplayGeometry != null && l.LedRect.Contains(scaledPosition));
|
||||
@ -317,35 +342,6 @@ namespace Artemis.UI.Shared
|
||||
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 />
|
||||
public void Dispose()
|
||||
@ -353,7 +349,5 @@ namespace Artemis.UI.Shared
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -37,10 +37,7 @@
|
||||
Background="{StaticResource Checkerboard}">
|
||||
<Rectangle Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp">
|
||||
<Rectangle.Fill>
|
||||
<LinearGradientBrush
|
||||
GradientStops="{Binding ColorGradient.Stops, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorGradientToGradientStopsConverter}}"
|
||||
EndPoint="1,0"
|
||||
StartPoint="0,0" />
|
||||
<LinearGradientBrush x:Name="GradientPreview" EndPoint="1,0" StartPoint="0,0" />
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
</Border>
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Properties;
|
||||
using Artemis.UI.Shared.Services;
|
||||
@ -17,12 +19,14 @@ namespace Artemis.UI.Shared
|
||||
{
|
||||
private static IColorPickerService? _colorPickerService;
|
||||
private bool _inCallback;
|
||||
private ColorGradientToGradientStopsConverter _gradientConverter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="GradientPicker" /> class
|
||||
/// </summary>
|
||||
public GradientPicker()
|
||||
{
|
||||
_gradientConverter = new ColorGradientToGradientStopsConverter();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@ -99,10 +103,27 @@ namespace Artemis.UI.Shared
|
||||
return;
|
||||
|
||||
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._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)
|
||||
{
|
||||
if (_colorPickerService == null)
|
||||
|
||||
@ -14,18 +14,18 @@ namespace Artemis.UI.Shared
|
||||
/// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a
|
||||
/// <see cref="T:System.Windows.Media.GradientStopCollection" />.
|
||||
/// </summary>
|
||||
[ValueConversion(typeof(List<ColorGradientStop>), typeof(GradientStopCollection))]
|
||||
[ValueConversion(typeof(ColorGradient), typeof(GradientStopCollection))]
|
||||
public class ColorGradientToGradientStopsConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
if (colorGradients == null)
|
||||
if (colorGradient == null)
|
||||
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));
|
||||
return collection;
|
||||
}
|
||||
@ -33,8 +33,8 @@ namespace Artemis.UI.Shared
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
GradientStopCollection collection = (GradientStopCollection) value;
|
||||
List<ColorGradientStop> colorGradients = new();
|
||||
GradientStopCollection? collection = value as GradientStopCollection;
|
||||
ColorGradient colorGradients = new();
|
||||
if (collection == null)
|
||||
return colorGradients;
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
{
|
||||
_gradientEditorViewModel = gradientEditorViewModel;
|
||||
ColorStop = colorStop;
|
||||
ColorStop.PropertyChanged += ColorStopOnPropertyChanged;
|
||||
}
|
||||
|
||||
public ColorGradientStop ColorStop { get; }
|
||||
@ -58,12 +57,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
get => _willRemoveColorStop;
|
||||
set => SetAndNotify(ref _willRemoveColorStop, value);
|
||||
}
|
||||
|
||||
private void ColorStopOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
_gradientEditorViewModel.ColorGradient.OnColorValuesUpdated();
|
||||
}
|
||||
|
||||
|
||||
#region Movement
|
||||
|
||||
public void StopMouseDown(object sender, MouseButtonEventArgs e)
|
||||
@ -102,7 +96,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
|
||||
double minValue = 0.0;
|
||||
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? next = stops.IndexOf(ColorStop) + 1 < stops.Count ? stops[stops.IndexOf(ColorStop) + 1] : null;
|
||||
if (previous != null)
|
||||
@ -111,7 +105,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
maxValue = next.Position * _gradientEditorViewModel.PreviewWidth;
|
||||
|
||||
Offset = Math.Max(minValue, Math.Min(maxValue, position.X));
|
||||
_gradientEditorViewModel.ColorGradient.OnColorValuesUpdated();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
<Rectangle x:Name="Preview" Height="40" shared:SizeObserver.Observe="True" shared:SizeObserver.ObservedWidth="{Binding PreviewWidth, Mode=OneWayToSource}">
|
||||
<Rectangle.Fill>
|
||||
<LinearGradientBrush
|
||||
GradientStops="{Binding ColorGradient.Stops, Converter={StaticResource ColorGradientToGradientStopsConverter}}"
|
||||
GradientStops="{Binding ColorGradient, Converter={StaticResource ColorGradientToGradientStopsConverter}}"
|
||||
EndPoint="1,0"
|
||||
StartPoint="0,0" />
|
||||
</Rectangle.Fill>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
@ -6,6 +7,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
@ -21,12 +23,24 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
ColorGradient = colorGradient;
|
||||
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;
|
||||
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
|
||||
{
|
||||
@ -48,15 +62,19 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
set => SetAndNotify(ref _previewWidth, value);
|
||||
}
|
||||
|
||||
public ColorGradient Stops
|
||||
{
|
||||
get => ColorGradient;
|
||||
}
|
||||
|
||||
public void AddColorStop(object sender, MouseEventArgs e)
|
||||
{
|
||||
Canvas? child = VisualTreeUtilities.FindChild<Canvas>((DependencyObject) sender, null);
|
||||
float position = (float) (e.GetPosition(child).X / PreviewWidth);
|
||||
ColorGradientStop stop = new(ColorGradient.GetColor(position), position);
|
||||
ColorGradient.Stops.Add(stop);
|
||||
ColorGradient.OnColorValuesUpdated();
|
||||
ColorGradient.Add(stop);
|
||||
|
||||
int index = ColorGradient.Stops.OrderBy(s => s.Position).ToList().IndexOf(stop);
|
||||
int index = ColorGradient.IndexOf(stop);
|
||||
ColorStopViewModel viewModel = new(this, stop);
|
||||
ColorStopViewModels.Insert(index, viewModel);
|
||||
|
||||
@ -69,8 +87,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
return;
|
||||
|
||||
ColorStopViewModels.Remove(colorStopViewModel);
|
||||
ColorGradient.Stops.Remove(colorStopViewModel.ColorStop);
|
||||
ColorGradient.OnColorValuesUpdated();
|
||||
ColorGradient.Remove(colorStopViewModel.ColorStop);
|
||||
|
||||
SelectColorStop(null);
|
||||
}
|
||||
@ -97,9 +114,9 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
public override void Cancel()
|
||||
{
|
||||
// Restore the saved state
|
||||
ColorGradient.Stops.Clear();
|
||||
ColorGradient.Stops.AddRange(_originalStops);
|
||||
ColorGradient.OnColorValuesUpdated();
|
||||
ColorGradient.Clear();
|
||||
foreach (ColorGradientStop colorGradientStop in _originalStops)
|
||||
ColorGradient.Add(colorGradientStop);
|
||||
|
||||
base.Cancel();
|
||||
}
|
||||
@ -107,8 +124,13 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
|
||||
private void UpdateColorStopViewModels(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(ColorGradient));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,6 @@ namespace Artemis.UI.Shared.Services
|
||||
{
|
||||
internal class ProfileEditorService : IProfileEditorService
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IProfileService _profileService;
|
||||
@ -28,6 +27,7 @@ namespace Artemis.UI.Shared.Services
|
||||
private readonly object _selectedProfileElementLock = new();
|
||||
private readonly object _selectedProfileLock = new();
|
||||
private TimeSpan _currentTime;
|
||||
private bool _doTick;
|
||||
private int _pixelsPerSecond;
|
||||
|
||||
public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService)
|
||||
@ -35,16 +35,63 @@ namespace Artemis.UI.Shared.Services
|
||||
_kernel = kernel;
|
||||
_logger = logger;
|
||||
_profileService = profileService;
|
||||
_coreService = coreService;
|
||||
_rgbService = rgbService;
|
||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||
|
||||
coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||
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)
|
||||
{
|
||||
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
|
||||
if (!_doTick) return;
|
||||
_doTick = false;
|
||||
Execute.PostToUIThread(OnProfilePreviewUpdated);
|
||||
}
|
||||
|
||||
@ -72,6 +119,26 @@ namespace Artemis.UI.Shared.Services
|
||||
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 bool Playing { get; set; }
|
||||
@ -86,7 +153,7 @@ namespace Artemis.UI.Shared.Services
|
||||
{
|
||||
if (_currentTime.Equals(value)) return;
|
||||
_currentTime = value;
|
||||
UpdateProfilePreview();
|
||||
Tick();
|
||||
OnCurrentTimeChanged();
|
||||
}
|
||||
}
|
||||
@ -180,16 +247,9 @@ namespace Artemis.UI.Shared.Services
|
||||
|
||||
public void UpdateProfilePreview()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
if (Playing)
|
||||
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);
|
||||
|
||||
_coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||
Tick();
|
||||
}
|
||||
|
||||
public bool UndoUpdateProfile()
|
||||
@ -357,6 +417,15 @@ namespace Artemis.UI.Shared.Services
|
||||
.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
|
||||
|
||||
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
|
||||
@ -436,70 +505,5 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,7 @@ namespace Artemis.UI
|
||||
|
||||
string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt"));
|
||||
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)
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Screens.ProfileEditor.Dialogs;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
@ -12,12 +14,15 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
|
||||
{
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IDialogService _dialogService;
|
||||
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)
|
||||
{
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_dialogService = dialogService;
|
||||
UpdateEnumValues();
|
||||
}
|
||||
|
||||
@ -43,7 +48,17 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
|
||||
protected override void OnInputValueApplied()
|
||||
{
|
||||
if (LayerProperty.ProfileElement is Layer layer)
|
||||
{
|
||||
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)
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance propertyInput:ColorGradientPropertyInputViewModel}">
|
||||
|
||||
@ -132,16 +132,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
||||
DisposeRightSideDynamicViewModel();
|
||||
if (RightSideInputViewModel == null)
|
||||
CreateRightSideInputViewModel();
|
||||
|
||||
|
||||
Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType();
|
||||
// Ensure the right static value is never null when the preferred type is a value type
|
||||
if (preferredType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
|
||||
RightSideInputViewModel.Value = preferredType.GetDefault();
|
||||
else
|
||||
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
|
||||
|
||||
if (RightSideInputViewModel.TargetType != preferredType)
|
||||
if (preferredType != null && RightSideInputViewModel.TargetType != 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,7 +73,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
|
||||
Modifier.DirectDataBinding.RemoveModifier(Modifier);
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
|
||||
private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
|
||||
{
|
||||
Modifier.UpdateParameterDynamic(e.DataModelPath);
|
||||
@ -139,13 +139,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
|
||||
// Parameter
|
||||
if (DynamicSelectionViewModel != null)
|
||||
DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath);
|
||||
else if (StaticInputViewModel != null)
|
||||
{
|
||||
// Ensure the right static value is never null when the preferred type is a value type
|
||||
else if (StaticInputViewModel != null)
|
||||
StaticInputViewModel.Value = Modifier.ParameterStaticValue;
|
||||
if (SelectedModifierType.ParameterType.IsValueType && StaticInputViewModel.Value == null)
|
||||
StaticInputViewModel.Value = SelectedModifierType.ParameterType.GetDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSelectModifierTypeCommand(object context)
|
||||
@ -197,6 +192,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
|
||||
private void DynamicSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -66,210 +66,188 @@
|
||||
<KeyBinding Command="{s:Action Play}" Key="Space" />
|
||||
<KeyBinding Command="{s:Action PlayFromStart}" Modifiers="Shift" Key="Space" />
|
||||
</UserControl.InputBindings>
|
||||
<Grid x:Name="ContainerGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left side -->
|
||||
<Grid Grid.Column="0">
|
||||
<materialDesign:DialogHost Identifier="LayerProperties" DialogTheme="Inherit">
|
||||
<Grid x:Name="ContainerGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Misc controls & time display -->
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="StepForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
|
||||
<StackPanel>
|
||||
<materialDesign:PackIcon Kind="Play"
|
||||
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="Pause"
|
||||
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipBackward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipPrevious" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipNext" />
|
||||
</Button>
|
||||
<shared:LockableToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||
IsChecked="{Binding Repeating}"
|
||||
IsLocked="True"
|
||||
Focusable="False"
|
||||
Click="{s:Action CycleRepeating}">
|
||||
<shared:LockableToggleButton.ToolTip>
|
||||
<!-- Left side -->
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Misc controls & time display -->
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="StepForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
|
||||
<StackPanel>
|
||||
<StackPanel Visibility="{Binding Repeating, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<TextBlock Text="Repeat entire timeline"
|
||||
Visibility="{Binding RepeatTimeline, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<TextBlock Text="Repeat segment"
|
||||
Visibility="{Binding RepeatSegment, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<TextBlock Visibility="{Binding Repeating, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
Don't repeat the timeline
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
This setting only applies to the editor and <Run Text="does not" FontWeight="Bold" /> affect the repeat mode during normal profile playback
|
||||
</TextBlock>
|
||||
<materialDesign:PackIcon Kind="Play"
|
||||
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="Pause"
|
||||
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</shared:LockableToggleButton.ToolTip>
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipBackward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipPrevious" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipNext" />
|
||||
</Button>
|
||||
<shared:LockableToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||
IsChecked="{Binding Repeating}"
|
||||
IsLocked="True"
|
||||
Focusable="False"
|
||||
Click="{s:Action CycleRepeating}">
|
||||
<shared:LockableToggleButton.ToolTip>
|
||||
<StackPanel>
|
||||
<StackPanel Visibility="{Binding Repeating, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<TextBlock Text="Repeat entire timeline"
|
||||
Visibility="{Binding RepeatTimeline, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<TextBlock Text="Repeat segment"
|
||||
Visibility="{Binding RepeatSegment, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<TextBlock Visibility="{Binding Repeating, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
Don't repeat the timeline
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
This setting only applies to the editor and <Run Text="does not" FontWeight="Bold" /> affect the repeat mode during normal profile playback
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</shared:LockableToggleButton.ToolTip>
|
||||
<Grid>
|
||||
<materialDesign:PackIcon Kind="Repeat"
|
||||
Height="24"
|
||||
Width="24"
|
||||
Visibility="{Binding RepeatTimeline, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="RepeatOne"
|
||||
Height="24"
|
||||
Width="24"
|
||||
Visibility="{Binding RepeatSegment, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
</shared:LockableToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
|
||||
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- Properties tree -->
|
||||
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
|
||||
<materialDesign:Transitioner x:Name="PropertyTreeTransitioner" SelectedIndex="{Binding PropertyTreeIndex}" DefaultTransitionOrigin="0.9, 0.9" AutoApplyTransitionOrigins="True">
|
||||
<ScrollViewer x:Name="PropertyTreeScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<ContentControl s:View.Model="{Binding TreeViewModel}"
|
||||
shared:SizeObserver.Observe="True"
|
||||
shared:SizeObserver.ObservedHeight="{Binding TreeViewModelHeight, Mode=OneWayToSource}" />
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid>
|
||||
<materialDesign:PackIcon Kind="Repeat"
|
||||
Height="24"
|
||||
Width="24"
|
||||
Visibility="{Binding RepeatTimeline, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="RepeatOne"
|
||||
Height="24"
|
||||
Width="24"
|
||||
Visibility="{Binding RepeatSegment, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
</shared:LockableToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
|
||||
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- Properties tree -->
|
||||
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
|
||||
<materialDesign:Transitioner x:Name="PropertyTreeTransitioner" SelectedIndex="{Binding PropertyTreeIndex}" DefaultTransitionOrigin="0.9, 0.9" AutoApplyTransitionOrigins="True">
|
||||
<ScrollViewer x:Name="PropertyTreeScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<ContentControl s:View.Model="{Binding TreeViewModel}"
|
||||
shared:SizeObserver.Observe="True"
|
||||
shared:SizeObserver.ObservedHeight="{Binding TreeViewModelHeight, Mode=OneWayToSource}"/>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid>
|
||||
<ContentControl s:View.Model="{Binding EffectsViewModel}" />
|
||||
<!-- Transitions only work when the command comes from inside the transitioner but we want the button outside,
|
||||
<ContentControl s:View.Model="{Binding EffectsViewModel}" />
|
||||
<!-- Transitions only work when the command comes from inside the transitioner but we want the button outside,
|
||||
by setting the command target to this hidden button we circumvent that -->
|
||||
<Button x:Name="TransitionCommandAnchor"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="110"
|
||||
Height="1"
|
||||
Visibility="Hidden" />
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
</materialDesign:Transitioner>
|
||||
</materialDesign:DialogHost>
|
||||
</Grid>
|
||||
<Button x:Name="TransitionCommandAnchor"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="110"
|
||||
Height="1"
|
||||
Visibility="Hidden" />
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
</materialDesign:Transitioner>
|
||||
</materialDesign:DialogHost>
|
||||
</Grid>
|
||||
|
||||
<!-- Resize -->
|
||||
<GridSplitter Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Width="15" HorizontalAlignment="Stretch" Cursor="SizeWE" Margin="-15 0" Background="Transparent" />
|
||||
<!-- Resize -->
|
||||
<GridSplitter Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Width="15" HorizontalAlignment="Stretch" Cursor="SizeWE" Margin="-15 0" Background="Transparent" />
|
||||
|
||||
<!-- Right side -->
|
||||
<materialDesign:Transitioner Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
x:Name="RightSideTransitioner"
|
||||
SelectedIndex="{Binding RightSideIndex}"
|
||||
DefaultTransitionOrigin="0, 0.5">
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid>
|
||||
<Grid.InputBindings>
|
||||
<KeyBinding Key="Delete" Command="{s:Action DeleteKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}"/>
|
||||
<KeyBinding Key="D" Modifiers="Control" Command="{s:Action DuplicateKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}"/>
|
||||
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}"/>
|
||||
</Grid.InputBindings>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Right side -->
|
||||
<materialDesign:Transitioner Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
x:Name="RightSideTransitioner"
|
||||
SelectedIndex="{Binding RightSideIndex}"
|
||||
DefaultTransitionOrigin="0, 0.5">
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid>
|
||||
<Grid.InputBindings>
|
||||
<KeyBinding Key="Delete" Command="{s:Action DeleteKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="D" Modifiers="Control" Command="{s:Action DuplicateKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
</Grid.InputBindings>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Timeline headers -->
|
||||
<ScrollViewer Grid.Row="0"
|
||||
x:Name="TimelineHeaderScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Canvas Background="{DynamicResource MaterialDesignCardBackground}" Width="{Binding ActualWidth, ElementName=PropertyTimeLine}">
|
||||
<!-- Timeline segments -->
|
||||
<ContentControl Canvas.Left="{Binding EndTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding EndTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding MainTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding MainTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding StartTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding StartTimelineSegmentViewModel}" />
|
||||
<!-- Timeline headers -->
|
||||
<ScrollViewer Grid.Row="0"
|
||||
x:Name="TimelineHeaderScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Canvas Background="{DynamicResource MaterialDesignCardBackground}" Width="{Binding ActualWidth, ElementName=PropertyTimeLine}">
|
||||
<!-- Timeline segments -->
|
||||
<ContentControl Canvas.Left="{Binding EndTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding EndTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding MainTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding MainTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding StartTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding StartTimelineSegmentViewModel}" />
|
||||
|
||||
<!-- Timeline header body -->
|
||||
<controls:PropertyTimelineHeader Margin="0 18 0 0"
|
||||
Foreground="{DynamicResource MaterialDesignBody}"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}"
|
||||
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
|
||||
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
|
||||
OffsetFirstValue="True"
|
||||
MouseLeftButtonUp="{s:Action TimelineJump}"
|
||||
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}"
|
||||
Cursor="Hand"/>
|
||||
<!-- Timeline header body -->
|
||||
<controls:PropertyTimelineHeader Margin="0 18 0 0"
|
||||
Foreground="{DynamicResource MaterialDesignBody}"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}"
|
||||
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
|
||||
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
|
||||
OffsetFirstValue="True"
|
||||
MouseLeftButtonUp="{s:Action TimelineJump}"
|
||||
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}"
|
||||
Cursor="Hand" />
|
||||
|
||||
<!-- Timeline caret -->
|
||||
<Polygon Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
||||
Fill="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Timeline rails -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
x:Name="TimelineRailsScrollViewer"
|
||||
Style="{StaticResource SvStyle}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}">
|
||||
<Canvas Grid.Column="0" Panel.ZIndex="1">
|
||||
<!-- Timeline caret -->
|
||||
<Polygon Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
||||
Fill="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
@ -278,169 +256,193 @@
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
|
||||
Y2="{Binding ActualHeight, ElementName=TimelineHeaderScrollViewer}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
</ScrollViewer>
|
||||
|
||||
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" x:Name="PropertyTimeLine" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
<!-- Timeline rails -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
x:Name="TimelineRailsScrollViewer"
|
||||
Style="{StaticResource SvStyle}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}" x:Name="RailsGrid">
|
||||
<Canvas Grid.Column="0" Panel.ZIndex="1">
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=RailsGrid}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid Background="{StaticResource MaterialDesignCardBackground}">
|
||||
<ContentControl s:View.Model="{Binding DataBindingsViewModel}" />
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
</materialDesign:Transitioner>
|
||||
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" x:Name="PropertyTimeLine" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
|
||||
<!-- Bottom row, a bit hacky but has a ZIndex of 2 to cut off the time caret that overlaps the entire timeline -->
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Panel.ZIndex="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Selected layer controls -->
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid Background="{StaticResource MaterialDesignCardBackground}">
|
||||
<ContentControl s:View.Model="{Binding DataBindingsViewModel}" />
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
</materialDesign:Transitioner>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
<!-- Bottom row, a bit hacky but has a ZIndex of 2 to cut off the time caret that overlaps the entire timeline -->
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Panel.ZIndex="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Selected layer controls -->
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Margin="6"
|
||||
Visibility="{Binding SelectedLayer, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<materialDesign:PackIcon Kind="Layers" Width="16" />
|
||||
<materialDesign:PackIcon Kind="{Binding SelectedLayer.LayerBrush.Descriptor.Icon}"
|
||||
Width="16"
|
||||
Margin="5 0 0 0"
|
||||
ToolTip="{Binding SelectedLayer.LayerBrush.Descriptor.DisplayName, Mode=OneWay}"
|
||||
Background="Transparent"
|
||||
Visibility="{Binding SelectedLayer.LayerBrush, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<TextBlock Text="{Binding SelectedLayer.Name}" Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Margin="6"
|
||||
Visibility="{Binding SelectedFolder, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<materialDesign:PackIcon Kind="Folder" Width="16" />
|
||||
<TextBlock Text="{Binding SelectedFolder.Name}" Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignFlatMidBgButton}"
|
||||
Margin="0 -2 5 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="115"
|
||||
ToolTip="Select an effect to add"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||
Command="{x:Static materialDesign:Transitioner.MoveLastCommand}"
|
||||
CommandTarget="{Binding ElementName=TransitionCommandAnchor}"
|
||||
Click="{s:Action ToggleEffectsViewModel}"
|
||||
IsEnabled="{Binding CanToggleEffectsViewModel}">
|
||||
<TextBlock FontSize="11">
|
||||
ADD EFFECT
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignFlatMidBgButton}"
|
||||
Margin="0 -2 5 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="115"
|
||||
ToolTip="Show the layer/folder properties"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}"
|
||||
Command="{x:Static materialDesign:Transitioner.MoveFirstCommand}"
|
||||
CommandTarget="{Binding ElementName=TransitionCommandAnchor}"
|
||||
Click="{s:Action ToggleEffectsViewModel}"
|
||||
IsEnabled="{Binding CanToggleEffectsViewModel}">
|
||||
<TextBlock FontSize="10">
|
||||
SHOW PROPERTIES
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Panel.ZIndex="2"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Margin="5 -2 0 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="110"
|
||||
ToolTip="Add a new segment to the timeline"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding SelectedProfileElement, Converter={StaticResource NullToBooleanConverter}}"
|
||||
Visibility="{Binding TimelineVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignFlatMidBgButton}">
|
||||
<Style.Triggers>
|
||||
<EventTrigger RoutedEvent="Click">
|
||||
<EventTrigger.Actions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger.Actions>
|
||||
</EventTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu.Resources>
|
||||
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
</ContextMenu.Resources>
|
||||
<MenuItem Header="Start"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Start"
|
||||
IsEnabled="{Binding Data.StartTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="Main"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Main"
|
||||
IsEnabled="{Binding Data.MainTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="End"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="End"
|
||||
IsEnabled="{Binding Data.EndTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<TextBlock FontSize="11">
|
||||
ADD SEGMENT
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Zoom control -->
|
||||
<Slider Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Margin="6"
|
||||
Visibility="{Binding SelectedLayer, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<materialDesign:PackIcon Kind="Layers" Width="16" />
|
||||
<materialDesign:PackIcon Kind="{Binding SelectedLayer.LayerBrush.Descriptor.Icon}"
|
||||
Width="16"
|
||||
Margin="5 0 0 0"
|
||||
ToolTip="{Binding SelectedLayer.LayerBrush.Descriptor.DisplayName, Mode=OneWay}"
|
||||
Background="Transparent"
|
||||
Visibility="{Binding SelectedLayer.LayerBrush, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<TextBlock Text="{Binding SelectedLayer.Name}" Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Margin="6"
|
||||
Visibility="{Binding SelectedFolder, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<materialDesign:PackIcon Kind="Folder" Width="16" />
|
||||
<TextBlock Text="{Binding SelectedFolder.Name}" Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignFlatMidBgButton}"
|
||||
Margin="0 -2 5 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="115"
|
||||
ToolTip="Select an effect to add"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||
Command="{x:Static materialDesign:Transitioner.MoveLastCommand}"
|
||||
CommandTarget="{Binding ElementName=TransitionCommandAnchor}"
|
||||
Click="{s:Action ToggleEffectsViewModel}"
|
||||
IsEnabled="{Binding CanToggleEffectsViewModel}">
|
||||
<TextBlock FontSize="11">
|
||||
ADD EFFECT
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignFlatMidBgButton}"
|
||||
Margin="0 -2 5 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="115"
|
||||
ToolTip="Show the layer/folder properties"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}"
|
||||
Command="{x:Static materialDesign:Transitioner.MoveFirstCommand}"
|
||||
CommandTarget="{Binding ElementName=TransitionCommandAnchor}"
|
||||
Click="{s:Action ToggleEffectsViewModel}"
|
||||
IsEnabled="{Binding CanToggleEffectsViewModel}">
|
||||
<TextBlock FontSize="10">
|
||||
SHOW PROPERTIES
|
||||
</TextBlock>
|
||||
</Button>
|
||||
HorizontalAlignment="Right"
|
||||
Margin="10 5"
|
||||
Minimum="31"
|
||||
Maximum="350"
|
||||
TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
AutoToolTipPlacement="TopLeft"
|
||||
Value="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
Width="319" />
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Panel.ZIndex="2"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Margin="5 -2 0 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="110"
|
||||
ToolTip="Add a new segment to the timeline"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding SelectedProfileElement, Converter={StaticResource NullToBooleanConverter}}"
|
||||
Visibility="{Binding TimelineVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignFlatMidBgButton}">
|
||||
<Style.Triggers>
|
||||
<EventTrigger RoutedEvent="Click">
|
||||
<EventTrigger.Actions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger.Actions>
|
||||
</EventTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu.Resources>
|
||||
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
</ContextMenu.Resources>
|
||||
<MenuItem Header="Start"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Start"
|
||||
IsEnabled="{Binding Data.StartTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="Main"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Main"
|
||||
IsEnabled="{Binding Data.MainTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="End"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="End"
|
||||
IsEnabled="{Binding Data.EndTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<TextBlock FontSize="11">
|
||||
ADD SEGMENT
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Zoom control -->
|
||||
<Slider Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="10 5"
|
||||
Minimum="31"
|
||||
Maximum="350"
|
||||
TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
AutoToolTipPlacement="TopLeft"
|
||||
Value="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
Width="319" />
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</UserControl>
|
||||
@ -32,7 +32,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private LayerBrushSettingsWindowViewModel? _layerBrushSettingsWindowVm;
|
||||
private LayerBrushSettingsWindowViewModel _layerBrushSettingsWindowVm;
|
||||
private LayerEffectSettingsWindowViewModel _layerEffectSettingsWindowVm;
|
||||
|
||||
public TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService, IDialogService dialogService, IWindowManager windowManager)
|
||||
|
||||
@ -47,7 +47,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
|
||||
|
||||
public void ResetToDefault()
|
||||
{
|
||||
LayerProperty.ApplyDefaultValue(_profileEditorService.CurrentTime);
|
||||
LayerProperty.ApplyDefaultValue();
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
|
||||
@ -321,7 +321,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
|
||||
dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta);
|
||||
|
||||
// TODO: Only update when there are data bindings
|
||||
_profileEditorService.UpdateProfilePreview();
|
||||
if (!_profileEditorService.Playing)
|
||||
_profileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
|
||||
private void HighlightSelectedLayerOnSettingChanged(object sender, EventArgs e)
|
||||
|
||||
@ -105,8 +105,8 @@
|
||||
</VisualBrush>
|
||||
</Grid.Background>
|
||||
|
||||
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
|
||||
<shared:DeviceVisualizer Device="{Binding Device}"
|
||||
HighlightedLeds="{Binding SelectedLeds}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ShowColors="True"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Xml.Serialization;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
@ -13,20 +13,28 @@ using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using RGB.NET.Layout;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Device
|
||||
{
|
||||
public class DeviceDialogViewModel : Conductor<Screen>.Collection.OneActive
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private ArtemisLed _selectedLed;
|
||||
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;
|
||||
_rgbService = rgbService;
|
||||
_dialogService = dialogService;
|
||||
@ -39,19 +47,8 @@ namespace Artemis.UI.Screens.Settings.Device
|
||||
Items.Add(factory.DeviceLedsTabViewModel(device));
|
||||
ActiveItem = Items.First();
|
||||
DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis";
|
||||
}
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
|
||||
Device.DeviceUpdated += DeviceOnDeviceUpdated;
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
Device.DeviceUpdated -= DeviceOnDeviceUpdated;
|
||||
base.OnClose();
|
||||
SelectedLeds = new BindableCollection<ArtemisLed>();
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
@ -63,29 +60,61 @@ namespace Artemis.UI.Screens.Settings.Device
|
||||
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 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 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
|
||||
|
||||
#region Command handlers
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
SelectedLed = null;
|
||||
SelectedLeds.Clear();
|
||||
}
|
||||
|
||||
public void IdentifyDevice()
|
||||
@ -190,19 +219,5 @@ namespace Artemis.UI.Screens.Settings.Device
|
||||
#endregion
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@ -4,34 +4,39 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
|
||||
xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<Converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/>
|
||||
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<DataGrid ItemsSource="{Binding Device.Leds}"
|
||||
d:DataContext="{d:DesignInstance Type={x:Type core:ArtemisLed}}"
|
||||
<DataGrid ItemsSource="{Binding LedViewModels}"
|
||||
d:DataContext="{d:DesignInstance Type={x:Type tabs:DeviceLedsTabLedViewModel}}"
|
||||
CanUserSortColumns="True"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
AutoGenerateColumns="False"
|
||||
materialDesign:DataGridAssist.CellPadding="5"
|
||||
materialDesign:DataGridAssist.ColumnHeaderPadding="5"
|
||||
SelectedItem="{Binding Parent.SelectedLed}"
|
||||
CanUserResizeRows="False"
|
||||
VirtualizingStackPanel.VirtualizationMode="Standard"
|
||||
Margin="10">
|
||||
<DataGrid.Resources>
|
||||
<Style TargetType="DataGridRow" BasedOn="{StaticResource MaterialDesignDataGridRow}">
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Id}" Header="LED ID" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Color}" Header="Color (ARGB)" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Shape}" Header="Shape" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Size}" Header="Size" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.CustomData}" Header="LED data" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id}" Header="LED ID" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color}" Header="Color (ARGB)" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Shape}" Header="Shape" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Size}" Header="Size" Width="Auto" />
|
||||
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.CustomData}" Header="LED data" Width="Auto" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
@ -1,17 +1,92 @@
|
||||
using Artemis.Core;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
{
|
||||
public class DeviceLedsTabViewModel : Screen
|
||||
{
|
||||
|
||||
public DeviceLedsTabViewModel(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
DisplayName = "LEDS";
|
||||
LedViewModels = new BindableCollection<DeviceLedsTabLedViewModel>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private bool _enabling;
|
||||
private readonly IMessageService _messageService;
|
||||
private bool _canToggleEnabled;
|
||||
|
||||
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
|
||||
bool showShield,
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
Title="Plugin configuration"
|
||||
Background="{DynamicResource MaterialDesignPaper}"
|
||||
@ -25,7 +26,7 @@
|
||||
<DockPanel>
|
||||
<controls:AppBar Type="Dense" Title="{Binding ActiveItem.Plugin.Info.Name}" DockPanel.Dock="Top" Margin="-18 0 0 0" ShowShadow="False">
|
||||
<controls:AppBar.AppIcon>
|
||||
<materialDesign:PackIcon Kind="{Binding Icon}" Width="20" Height="28" />
|
||||
<shared:ArtemisIcon Icon="{Binding Icon}"></shared:ArtemisIcon>
|
||||
</controls:AppBar.AppIcon>
|
||||
</controls:AppBar>
|
||||
|
||||
|
||||
@ -25,5 +25,23 @@ namespace Artemis.UI.Screens.StartupWizard.Steps
|
||||
StartupWizardViewModel startupWizardViewModel = (StartupWizardViewModel) Parent;
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -106,7 +106,7 @@ namespace Artemis.UI.Services
|
||||
|
||||
public void RegisterControllers()
|
||||
{
|
||||
_webServerService.AddController<RemoteController>();
|
||||
_webServerService.AddController<RemoteController>(Constants.CorePlugin.Features.First().Instance!);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user