mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Profiles - Added enter, main and exit timelines
Display conditions - Fleshed out most of the UI
This commit is contained in:
1 parent
0e873a48cf
commit
7cfe9a46ee
26 files changed
+282
-75
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
@@ -34,9 +35,10 @@ namespace Artemis.Core.Models.Profile.Conditions.Abstract
|
||||
}
|
||||
}
|
||||
|
||||
public abstract DisplayConditionPartEntity GetEntity();
|
||||
public abstract bool Evaluate();
|
||||
|
||||
internal abstract void ApplyToEntity();
|
||||
internal abstract void Initialize(IDataModelService dataModelService);
|
||||
internal abstract void ApplyToEntity();
|
||||
internal abstract DisplayConditionPartEntity GetEntity();
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,23 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
public BooleanOperator BooleanOperator { get; set; }
|
||||
public DisplayConditionGroupEntity DisplayConditionGroupEntity { get; set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
switch (BooleanOperator)
|
||||
{
|
||||
case BooleanOperator.And:
|
||||
return Children.All(c => c.Evaluate());
|
||||
case BooleanOperator.Or:
|
||||
return Children.Any(c => c.Evaluate());
|
||||
case BooleanOperator.AndNot:
|
||||
return Children.All(c => !c.Evaluate());
|
||||
case BooleanOperator.OrNot:
|
||||
return Children.Any(c => !c.Evaluate());
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator;
|
||||
@@ -49,7 +66,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
child.Initialize(dataModelService);
|
||||
}
|
||||
|
||||
public override DisplayConditionPartEntity GetEntity()
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionGroupEntity;
|
||||
}
|
||||
|
||||
@@ -8,18 +8,24 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
public ListOperator ListOperator { get; set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
}
|
||||
|
||||
public override DisplayConditionPartEntity GetEntity()
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public enum ListOperator
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
ValidateOperator();
|
||||
ValidateRightSide();
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
@@ -91,6 +92,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
RightPropertyPath = null;
|
||||
|
||||
SetStaticValue(staticValue);
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
@@ -111,14 +113,21 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
var leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath);
|
||||
if (displayConditionOperator.SupportsType(leftType))
|
||||
Operator = displayConditionOperator;
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void CreateExpression()
|
||||
private void CreateExpression()
|
||||
{
|
||||
DynamicConditionLambda = null;
|
||||
CompiledDynamicConditionLambda = null;
|
||||
StaticConditionLambda = null;
|
||||
CompiledStaticConditionLambda = null;
|
||||
|
||||
if (PredicateType == PredicateType.Dynamic)
|
||||
CreateDynamicExpression();
|
||||
else
|
||||
CreateStaticExpression();
|
||||
|
||||
CreateStaticExpression();
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
@@ -134,6 +143,16 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name;
|
||||
}
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (CompiledDynamicConditionLambda != null)
|
||||
return CompiledDynamicConditionLambda(LeftDataModel, RightDataModel);
|
||||
if (CompiledStaticConditionLambda != null)
|
||||
return CompiledStaticConditionLambda(LeftDataModel);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Left side
|
||||
@@ -184,7 +203,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
}
|
||||
}
|
||||
|
||||
public override DisplayConditionPartEntity GetEntity()
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionPredicateEntity;
|
||||
}
|
||||
@@ -199,9 +218,6 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the right side, ensuring it is still compatible with the current left side
|
||||
/// </summary>
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
var leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath);
|
||||
@@ -223,10 +239,6 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current static value, ensuring it is a valid type. This assumes the types are compatible if they
|
||||
/// differ.
|
||||
/// </summary>
|
||||
private void SetStaticValue(object staticValue)
|
||||
{
|
||||
// If the left side is empty simply apply the value, any validation will wait
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace Artemis.Core.Models.Profile
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
UpdateDisplayCondition();
|
||||
|
||||
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||
baseLayerEffect.Update(deltaTime);
|
||||
|
||||
|
||||
@@ -205,6 +205,8 @@ namespace Artemis.Core.Models.Profile
|
||||
if (LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized)
|
||||
return;
|
||||
|
||||
UpdateDisplayCondition();
|
||||
|
||||
// TODO: Remove, this is slow and stupid
|
||||
// For now, reset all keyframe engines after the last keyframe was hit
|
||||
// This is a placeholder method of repeating the animation until repeat modes are implemented
|
||||
|
||||
@@ -18,6 +18,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// </summary>
|
||||
public BaseLayerProperty BaseLayerProperty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeline this keyframe is contained in
|
||||
/// </summary>
|
||||
public abstract Timeline Timeline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position of this keyframe in the timeline
|
||||
/// </summary>
|
||||
@@ -28,4 +33,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// </summary>
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
}
|
||||
|
||||
public enum Timeline
|
||||
{
|
||||
Start,
|
||||
Main,
|
||||
End
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,16 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
private T _currentValue;
|
||||
private bool _isInitialized;
|
||||
private List<LayerPropertyKeyframe<T>> _keyframes;
|
||||
private List<LayerPropertyKeyframe<T>> _startKeyframes;
|
||||
private List<LayerPropertyKeyframe<T>> _mainKeyframes;
|
||||
private List<LayerPropertyKeyframe<T>> _endKeyframes;
|
||||
|
||||
protected LayerProperty()
|
||||
{
|
||||
_keyframes = new List<LayerPropertyKeyframe<T>>();
|
||||
_startKeyframes = new List<LayerPropertyKeyframe<T>>();
|
||||
_mainKeyframes = new List<LayerPropertyKeyframe<T>>();
|
||||
_endKeyframes = new List<LayerPropertyKeyframe<T>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,7 +90,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="time">
|
||||
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
|
||||
/// or existing keyframe.
|
||||
/// or existing keyframe in the currently active timeline.
|
||||
/// </param>
|
||||
public void SetCurrentValue(T value, TimeSpan? time)
|
||||
{
|
||||
@@ -93,10 +99,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
else
|
||||
{
|
||||
// If on a keyframe, update the keyframe
|
||||
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
|
||||
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value && k.Timeline == ProfileElement.CurrentTimeline);
|
||||
// Create a new keyframe if none found
|
||||
if (currentKeyframe == null)
|
||||
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
|
||||
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, ProfileElement.CurrentTimeline, Easings.Functions.Linear, this));
|
||||
else
|
||||
currentKeyframe.Value = value;
|
||||
|
||||
@@ -132,6 +138,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
var newKeyframe = new LayerPropertyKeyframe<T>(
|
||||
keyframe.Value,
|
||||
keyframe.Position,
|
||||
keyframe.Timeline,
|
||||
keyframe.EasingFunction,
|
||||
keyframe.LayerProperty
|
||||
);
|
||||
@@ -193,22 +200,42 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
if (!KeyframesSupported || !KeyframesEnabled)
|
||||
return;
|
||||
|
||||
var keyframeSet = _keyframes;
|
||||
if (ProfileElement.CurrentTimeline == Timeline.Start)
|
||||
keyframeSet = _startKeyframes;
|
||||
else if (ProfileElement.CurrentTimeline == Timeline.Main)
|
||||
keyframeSet = _mainKeyframes;
|
||||
else if (ProfileElement.CurrentTimeline == Timeline.End)
|
||||
keyframeSet = _endKeyframes;
|
||||
|
||||
// The current keyframe is the last keyframe before the current time
|
||||
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress);
|
||||
CurrentKeyframe = keyframeSet.LastOrDefault(k => k.Position <= TimelineProgress);
|
||||
|
||||
// If the current keyframe is null, try to find it in previous timelines
|
||||
if (CurrentKeyframe == null && ProfileElement.CurrentTimeline == Timeline.Main)
|
||||
CurrentKeyframe = _startKeyframes.LastOrDefault();
|
||||
else if (CurrentKeyframe == null && ProfileElement.CurrentTimeline == Timeline.End)
|
||||
CurrentKeyframe = _mainKeyframes.LastOrDefault() ?? _startKeyframes.LastOrDefault();
|
||||
|
||||
// Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current
|
||||
var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
|
||||
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
|
||||
var nextIndex = keyframeSet.IndexOf(CurrentKeyframe) + 1;
|
||||
NextKeyframe = keyframeSet.Count > nextIndex ? keyframeSet[nextIndex] : null;
|
||||
|
||||
// No need to update the current value if either of the keyframes are null
|
||||
if (CurrentKeyframe == null)
|
||||
CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue;
|
||||
CurrentValue = keyframeSet.Any() ? keyframeSet[0].Value : BaseValue;
|
||||
else if (NextKeyframe == null)
|
||||
CurrentValue = CurrentKeyframe.Value;
|
||||
// Only determine progress and current value if both keyframes are present
|
||||
else
|
||||
{
|
||||
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
||||
var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
||||
// If the current keyframe belongs to a previous timeline, consider it starting at 0
|
||||
var currentKeyframePosition = CurrentKeyframe.Position;
|
||||
if (CurrentKeyframe.Timeline != ProfileElement.CurrentTimeline)
|
||||
currentKeyframePosition = TimeSpan.Zero;
|
||||
|
||||
var timeDiff = NextKeyframe.Position - currentKeyframePosition;
|
||||
var keyframeProgress = (float) ((TimelineProgress - currentKeyframePosition).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
||||
var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
|
||||
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
|
||||
}
|
||||
@@ -227,11 +254,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the keyframes in ascending order by position
|
||||
/// Sorts the keyframes in ascending order by position and divides the keyframes into different timelines
|
||||
/// </summary>
|
||||
internal void SortKeyframes()
|
||||
{
|
||||
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
|
||||
|
||||
_startKeyframes = _keyframes.Where(k => k.Timeline == Timeline.Start).ToList();
|
||||
_mainKeyframes = _keyframes.Where(k => k.Timeline == Timeline.Main).ToList();
|
||||
_endKeyframes = _keyframes.Where(k => k.Timeline == Timeline.End).ToList();
|
||||
}
|
||||
|
||||
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
|
||||
@@ -258,6 +289,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
_keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
|
||||
JsonConvert.DeserializeObject<T>(k.Value),
|
||||
k.Position,
|
||||
(Timeline) k.Timeline,
|
||||
(Easings.Functions) k.EasingFunction,
|
||||
this
|
||||
)));
|
||||
@@ -287,6 +319,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
Value = JsonConvert.SerializeObject(k.Value),
|
||||
Position = k.Position,
|
||||
Timeline = (int) k.Timeline,
|
||||
EasingFunction = (int) k.EasingFunction
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
public class LayerPropertyKeyframe<T> : BaseLayerPropertyKeyframe
|
||||
{
|
||||
private TimeSpan _position;
|
||||
private Timeline _timeline;
|
||||
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Timeline timeline, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
|
||||
{
|
||||
_position = position;
|
||||
_timeline = timeline;
|
||||
Value = value;
|
||||
LayerProperty = layerProperty;
|
||||
EasingFunction = easingFunction;
|
||||
@@ -25,6 +27,17 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// </summary>
|
||||
public T Value { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Timeline Timeline
|
||||
{
|
||||
get => _timeline;
|
||||
set
|
||||
{
|
||||
_timeline = value;
|
||||
LayerProperty.SortKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TimeSpan Position
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Annotations;
|
||||
using Artemis.Core.Models.Profile.Conditions;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Plugins.LayerEffect.Abstract;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
@@ -18,6 +19,11 @@ namespace Artemis.Core.Models.Profile
|
||||
private SKPath _path;
|
||||
internal abstract RenderElementEntity RenderElementEntity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently active timeline
|
||||
/// </summary>
|
||||
public Timeline CurrentTimeline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
|
||||
/// clipped.
|
||||
@@ -142,6 +148,11 @@ namespace Artemis.Core.Models.Profile
|
||||
set => SetAndNotify(ref _displayConditionGroup, value);
|
||||
}
|
||||
|
||||
public void UpdateDisplayCondition()
|
||||
{
|
||||
var rootGroupResult = DisplayConditionGroup.Evaluate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
@@ -19,10 +19,10 @@ namespace Artemis.Core.Services.Storage
|
||||
/// </summary>
|
||||
public class ProfileService : IProfileService
|
||||
{
|
||||
private readonly IRenderElementService _renderElementService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginService _pluginService;
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IRenderElementService _renderElementService;
|
||||
private readonly ISurfaceService _surfaceService;
|
||||
|
||||
internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IRenderElementService renderElementService, IProfileRepository profileRepository)
|
||||
@@ -39,6 +39,8 @@ namespace Artemis.Core.Services.Storage
|
||||
_pluginService.PluginDisabled += OnPluginToggled;
|
||||
}
|
||||
|
||||
public JsonSerializerSettings MementoSettings { get; set; } = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
|
||||
|
||||
public void ActivateDefaultProfiles()
|
||||
{
|
||||
foreach (var profileModule in _pluginService.GetPluginsOfType<ProfileModule>())
|
||||
@@ -109,7 +111,7 @@ namespace Artemis.Core.Services.Storage
|
||||
public void UpdateProfile(Profile profile, bool includeChildren)
|
||||
{
|
||||
_logger.Debug("Updating profile " + profile);
|
||||
var memento = JsonConvert.SerializeObject(profile.ProfileEntity);
|
||||
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
profile.RedoStack.Clear();
|
||||
profile.UndoStack.Push(memento);
|
||||
|
||||
@@ -135,9 +137,9 @@ namespace Artemis.Core.Services.Storage
|
||||
|
||||
ActivateProfile(module, null);
|
||||
var top = profile.UndoStack.Pop();
|
||||
var memento = JsonConvert.SerializeObject(profile.ProfileEntity);
|
||||
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
profile.RedoStack.Push(memento);
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top);
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
|
||||
profile.ApplyToProfile();
|
||||
ActivateProfile(module, profile);
|
||||
|
||||
@@ -155,9 +157,9 @@ namespace Artemis.Core.Services.Storage
|
||||
|
||||
ActivateProfile(module, null);
|
||||
var top = profile.RedoStack.Pop();
|
||||
var memento = JsonConvert.SerializeObject(profile.ProfileEntity);
|
||||
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
profile.UndoStack.Push(memento);
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top);
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
|
||||
profile.ApplyToProfile();
|
||||
ActivateProfile(module, profile);
|
||||
|
||||
|
||||
Reference in new issue
Block a user