1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 09:43:46 +00:00

Profiles - Added enter, main and exit timelines

Display conditions - Fleshed out most of the UI
This commit is contained in:
SpoinkyNL 2020-07-12 22:27:30 +02:00
parent 0e873a48cf
commit 7cfe9a46ee
26 changed files with 282 additions and 75 deletions

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Entities.Profile.Abstract; 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 Initialize(IDataModelService dataModelService);
internal abstract void ApplyToEntity();
internal abstract DisplayConditionPartEntity GetEntity();
} }
} }

View File

@ -33,6 +33,23 @@ namespace Artemis.Core.Models.Profile.Conditions
public BooleanOperator BooleanOperator { get; set; } public BooleanOperator BooleanOperator { get; set; }
public DisplayConditionGroupEntity DisplayConditionGroupEntity { 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() internal override void ApplyToEntity()
{ {
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator; DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator;
@ -49,7 +66,7 @@ namespace Artemis.Core.Models.Profile.Conditions
child.Initialize(dataModelService); child.Initialize(dataModelService);
} }
public override DisplayConditionPartEntity GetEntity() internal override DisplayConditionPartEntity GetEntity()
{ {
return DisplayConditionGroupEntity; return DisplayConditionGroupEntity;
} }

View File

@ -8,18 +8,24 @@ namespace Artemis.Core.Models.Profile.Conditions
{ {
public ListOperator ListOperator { get; set; } public ListOperator ListOperator { get; set; }
public override bool Evaluate()
{
return true;
}
internal override void ApplyToEntity() internal override void ApplyToEntity()
{ {
} }
internal override void Initialize(IDataModelService dataModelService) internal override DisplayConditionPartEntity GetEntity()
{
}
public override DisplayConditionPartEntity GetEntity()
{ {
return null; return null;
} }
internal override void Initialize(IDataModelService dataModelService)
{
}
} }
public enum ListOperator public enum ListOperator

View File

@ -61,6 +61,7 @@ namespace Artemis.Core.Models.Profile.Conditions
ValidateOperator(); ValidateOperator();
ValidateRightSide(); ValidateRightSide();
CreateExpression(); CreateExpression();
} }
@ -91,6 +92,7 @@ namespace Artemis.Core.Models.Profile.Conditions
RightPropertyPath = null; RightPropertyPath = null;
SetStaticValue(staticValue); SetStaticValue(staticValue);
CreateExpression(); CreateExpression();
} }
@ -111,14 +113,21 @@ namespace Artemis.Core.Models.Profile.Conditions
var leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); var leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath);
if (displayConditionOperator.SupportsType(leftType)) if (displayConditionOperator.SupportsType(leftType))
Operator = displayConditionOperator; Operator = displayConditionOperator;
CreateExpression();
} }
public void CreateExpression() private void CreateExpression()
{ {
DynamicConditionLambda = null;
CompiledDynamicConditionLambda = null;
StaticConditionLambda = null;
CompiledStaticConditionLambda = null;
if (PredicateType == PredicateType.Dynamic) if (PredicateType == PredicateType.Dynamic)
CreateDynamicExpression(); CreateDynamicExpression();
else
CreateStaticExpression(); CreateStaticExpression();
} }
internal override void ApplyToEntity() internal override void ApplyToEntity()
@ -134,6 +143,16 @@ namespace Artemis.Core.Models.Profile.Conditions
DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name; 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) internal override void Initialize(IDataModelService dataModelService)
{ {
// Left side // Left side
@ -184,7 +203,7 @@ namespace Artemis.Core.Models.Profile.Conditions
} }
} }
public override DisplayConditionPartEntity GetEntity() internal override DisplayConditionPartEntity GetEntity()
{ {
return DisplayConditionPredicateEntity; return DisplayConditionPredicateEntity;
} }
@ -199,9 +218,6 @@ namespace Artemis.Core.Models.Profile.Conditions
Operator = null; Operator = null;
} }
/// <summary>
/// Validates the right side, ensuring it is still compatible with the current left side
/// </summary>
private void ValidateRightSide() private void ValidateRightSide()
{ {
var leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); 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) private void SetStaticValue(object staticValue)
{ {
// If the left side is empty simply apply the value, any validation will wait // If the left side is empty simply apply the value, any validation will wait

View File

@ -65,6 +65,8 @@ namespace Artemis.Core.Models.Profile
if (!Enabled) if (!Enabled)
return; return;
UpdateDisplayCondition();
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.Update(deltaTime); baseLayerEffect.Update(deltaTime);

View File

@ -205,6 +205,8 @@ namespace Artemis.Core.Models.Profile
if (LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) if (LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized)
return; return;
UpdateDisplayCondition();
// TODO: Remove, this is slow and stupid // TODO: Remove, this is slow and stupid
// For now, reset all keyframe engines after the last keyframe was hit // 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 // This is a placeholder method of repeating the animation until repeat modes are implemented

View File

@ -18,6 +18,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
/// </summary> /// </summary>
public BaseLayerProperty BaseLayerProperty { get; internal set; } public BaseLayerProperty BaseLayerProperty { get; internal set; }
/// <summary>
/// The timeline this keyframe is contained in
/// </summary>
public abstract Timeline Timeline { get; set; }
/// <summary> /// <summary>
/// The position of this keyframe in the timeline /// The position of this keyframe in the timeline
/// </summary> /// </summary>
@ -28,4 +33,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
/// </summary> /// </summary>
public Easings.Functions EasingFunction { get; set; } public Easings.Functions EasingFunction { get; set; }
} }
public enum Timeline
{
Start,
Main,
End
}
} }

View File

@ -23,10 +23,16 @@ namespace Artemis.Core.Models.Profile.LayerProperties
private T _currentValue; private T _currentValue;
private bool _isInitialized; private bool _isInitialized;
private List<LayerPropertyKeyframe<T>> _keyframes; private List<LayerPropertyKeyframe<T>> _keyframes;
private List<LayerPropertyKeyframe<T>> _startKeyframes;
private List<LayerPropertyKeyframe<T>> _mainKeyframes;
private List<LayerPropertyKeyframe<T>> _endKeyframes;
protected LayerProperty() protected LayerProperty()
{ {
_keyframes = new List<LayerPropertyKeyframe<T>>(); _keyframes = new List<LayerPropertyKeyframe<T>>();
_startKeyframes = new List<LayerPropertyKeyframe<T>>();
_mainKeyframes = new List<LayerPropertyKeyframe<T>>();
_endKeyframes = new List<LayerPropertyKeyframe<T>>();
} }
/// <summary> /// <summary>
@ -84,7 +90,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
/// <param name="value">The value to set.</param> /// <param name="value">The value to set.</param>
/// <param name="time"> /// <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 /// 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> /// </param>
public void SetCurrentValue(T value, TimeSpan? time) public void SetCurrentValue(T value, TimeSpan? time)
{ {
@ -93,10 +99,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties
else else
{ {
// If on a keyframe, update the keyframe // 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 // Create a new keyframe if none found
if (currentKeyframe == null) 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 else
currentKeyframe.Value = value; currentKeyframe.Value = value;
@ -132,6 +138,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
var newKeyframe = new LayerPropertyKeyframe<T>( var newKeyframe = new LayerPropertyKeyframe<T>(
keyframe.Value, keyframe.Value,
keyframe.Position, keyframe.Position,
keyframe.Timeline,
keyframe.EasingFunction, keyframe.EasingFunction,
keyframe.LayerProperty keyframe.LayerProperty
); );
@ -193,22 +200,42 @@ namespace Artemis.Core.Models.Profile.LayerProperties
if (!KeyframesSupported || !KeyframesEnabled) if (!KeyframesSupported || !KeyframesEnabled)
return; 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 // 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 // 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; var nextIndex = keyframeSet.IndexOf(CurrentKeyframe) + 1;
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; NextKeyframe = keyframeSet.Count > nextIndex ? keyframeSet[nextIndex] : null;
// No need to update the current value if either of the keyframes are null // No need to update the current value if either of the keyframes are null
if (CurrentKeyframe == null) if (CurrentKeyframe == null)
CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue; CurrentValue = keyframeSet.Any() ? keyframeSet[0].Value : BaseValue;
else if (NextKeyframe == null) else if (NextKeyframe == null)
CurrentValue = CurrentKeyframe.Value; CurrentValue = CurrentKeyframe.Value;
// Only determine progress and current value if both keyframes are present // Only determine progress and current value if both keyframes are present
else else
{ {
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; // If the current keyframe belongs to a previous timeline, consider it starting at 0
var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); 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); var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
UpdateCurrentValue(keyframeProgress, keyframeProgressEased); UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
} }
@ -227,11 +254,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties
} }
/// <summary> /// <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> /// </summary>
internal void SortKeyframes() internal void SortKeyframes()
{ {
_keyframes = _keyframes.OrderBy(k => k.Position).ToList(); _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) 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>( _keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
JsonConvert.DeserializeObject<T>(k.Value), JsonConvert.DeserializeObject<T>(k.Value),
k.Position, k.Position,
(Timeline) k.Timeline,
(Easings.Functions) k.EasingFunction, (Easings.Functions) k.EasingFunction,
this this
))); )));
@ -287,6 +319,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
{ {
Value = JsonConvert.SerializeObject(k.Value), Value = JsonConvert.SerializeObject(k.Value),
Position = k.Position, Position = k.Position,
Timeline = (int) k.Timeline,
EasingFunction = (int) k.EasingFunction EasingFunction = (int) k.EasingFunction
})); }));
} }

View File

@ -6,10 +6,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties
public class LayerPropertyKeyframe<T> : BaseLayerPropertyKeyframe public class LayerPropertyKeyframe<T> : BaseLayerPropertyKeyframe
{ {
private TimeSpan _position; 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; _position = position;
_timeline = timeline;
Value = value; Value = value;
LayerProperty = layerProperty; LayerProperty = layerProperty;
EasingFunction = easingFunction; EasingFunction = easingFunction;
@ -25,6 +27,17 @@ namespace Artemis.Core.Models.Profile.LayerProperties
/// </summary> /// </summary>
public T Value { get; set; } public T Value { get; set; }
/// <inheritdoc />
public override Timeline Timeline
{
get => _timeline;
set
{
_timeline = value;
LayerProperty.SortKeyframes();
}
}
/// <inheritdoc /> /// <inheritdoc />
public override TimeSpan Position public override TimeSpan Position
{ {

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.Annotations; using Artemis.Core.Annotations;
using Artemis.Core.Models.Profile.Conditions; using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Plugins.LayerEffect.Abstract;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
@ -18,6 +19,11 @@ namespace Artemis.Core.Models.Profile
private SKPath _path; private SKPath _path;
internal abstract RenderElementEntity RenderElementEntity { get; } internal abstract RenderElementEntity RenderElementEntity { get; }
/// <summary>
/// Gets or sets the currently active timeline
/// </summary>
public Timeline CurrentTimeline { get; set; }
/// <summary> /// <summary>
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped. /// clipped.
@ -142,6 +148,11 @@ namespace Artemis.Core.Models.Profile
set => SetAndNotify(ref _displayConditionGroup, value); set => SetAndNotify(ref _displayConditionGroup, value);
} }
public void UpdateDisplayCondition()
{
var rootGroupResult = DisplayConditionGroup.Evaluate();
}
#endregion #endregion
#region Events #region Events

View File

@ -19,10 +19,10 @@ namespace Artemis.Core.Services.Storage
/// </summary> /// </summary>
public class ProfileService : IProfileService public class ProfileService : IProfileService
{ {
private readonly IRenderElementService _renderElementService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IPluginService _pluginService; private readonly IPluginService _pluginService;
private readonly IProfileRepository _profileRepository; private readonly IProfileRepository _profileRepository;
private readonly IRenderElementService _renderElementService;
private readonly ISurfaceService _surfaceService; private readonly ISurfaceService _surfaceService;
internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IRenderElementService renderElementService, IProfileRepository profileRepository) internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IRenderElementService renderElementService, IProfileRepository profileRepository)
@ -39,6 +39,8 @@ namespace Artemis.Core.Services.Storage
_pluginService.PluginDisabled += OnPluginToggled; _pluginService.PluginDisabled += OnPluginToggled;
} }
public JsonSerializerSettings MementoSettings { get; set; } = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
public void ActivateDefaultProfiles() public void ActivateDefaultProfiles()
{ {
foreach (var profileModule in _pluginService.GetPluginsOfType<ProfileModule>()) foreach (var profileModule in _pluginService.GetPluginsOfType<ProfileModule>())
@ -109,7 +111,7 @@ namespace Artemis.Core.Services.Storage
public void UpdateProfile(Profile profile, bool includeChildren) public void UpdateProfile(Profile profile, bool includeChildren)
{ {
_logger.Debug("Updating profile " + profile); _logger.Debug("Updating profile " + profile);
var memento = JsonConvert.SerializeObject(profile.ProfileEntity); var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.RedoStack.Clear(); profile.RedoStack.Clear();
profile.UndoStack.Push(memento); profile.UndoStack.Push(memento);
@ -135,9 +137,9 @@ namespace Artemis.Core.Services.Storage
ActivateProfile(module, null); ActivateProfile(module, null);
var top = profile.UndoStack.Pop(); var top = profile.UndoStack.Pop();
var memento = JsonConvert.SerializeObject(profile.ProfileEntity); var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.RedoStack.Push(memento); profile.RedoStack.Push(memento);
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top); profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
profile.ApplyToProfile(); profile.ApplyToProfile();
ActivateProfile(module, profile); ActivateProfile(module, profile);
@ -155,9 +157,9 @@ namespace Artemis.Core.Services.Storage
ActivateProfile(module, null); ActivateProfile(module, null);
var top = profile.RedoStack.Pop(); var top = profile.RedoStack.Pop();
var memento = JsonConvert.SerializeObject(profile.ProfileEntity); var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.UndoStack.Push(memento); profile.UndoStack.Push(memento);
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top); profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
profile.ApplyToProfile(); profile.ApplyToProfile();
ActivateProfile(module, profile); ActivateProfile(module, profile);

View File

@ -22,6 +22,7 @@ namespace Artemis.Storage.Entities.Profile
public class KeyframeEntity public class KeyframeEntity
{ {
public TimeSpan Position { get; set; } public TimeSpan Position { get; set; }
public int Timeline { get; set; }
public string Value { get; set; } public string Value { get; set; }
public int EasingFunction { get; set; } public int EasingFunction { get; set; }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Models; using Artemis.Core.Plugins.Models;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
@ -14,6 +15,7 @@ namespace Artemis.UI.Shared.Services.Interfaces
Profile SelectedProfile { get; } Profile SelectedProfile { get; }
RenderProfileElement SelectedProfileElement { get; } RenderProfileElement SelectedProfileElement { get; }
TimeSpan CurrentTime { get; set; } TimeSpan CurrentTime { get; set; }
Timeline CurrentTimeline { get; set; }
int PixelsPerSecond { get; set; } int PixelsPerSecond { get; set; }
IReadOnlyList<PropertyInputRegistration> RegisteredPropertyEditors { get; } IReadOnlyList<PropertyInputRegistration> RegisteredPropertyEditors { get; }
IKernel Kernel { get; } IKernel Kernel { get; }
@ -54,6 +56,11 @@ namespace Artemis.UI.Shared.Services.Interfaces
/// </summary> /// </summary>
event EventHandler CurrentTimeChanged; event EventHandler CurrentTimeChanged;
/// <summary>
/// Occurs when the current editor timeline is changed
/// </summary>
event EventHandler CurrentTimelineChanged;
/// <summary> /// <summary>
/// Occurs when the pixels per second (zoom level) is changed /// Occurs when the pixels per second (zoom level) is changed
/// </summary> /// </summary>

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Plugins.Exceptions;
using Artemis.Core.Plugins.Models; using Artemis.Core.Plugins.Models;
@ -24,6 +25,7 @@ namespace Artemis.UI.Shared.Services
private TimeSpan _currentTime; private TimeSpan _currentTime;
private TimeSpan _lastUpdateTime; private TimeSpan _lastUpdateTime;
private int _pixelsPerSecond; private int _pixelsPerSecond;
private Timeline _currentTimeline;
public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel, ILogger logger) public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel, ILogger logger)
{ {
@ -46,14 +48,25 @@ namespace Artemis.UI.Shared.Services
get => _currentTime; get => _currentTime;
set set
{ {
if (_currentTime.Equals(value)) if (_currentTime.Equals(value)) return;
return;
_currentTime = value; _currentTime = value;
UpdateProfilePreview(); UpdateProfilePreview();
OnCurrentTimeChanged(); OnCurrentTimeChanged();
} }
} }
public Timeline CurrentTimeline
{
get => _currentTimeline;
set
{
if (_currentTimeline.Equals(value)) return;
_currentTimeline = value;
UpdateProfilePreview();
OnCurrentTimelineChanged();
}
}
public int PixelsPerSecond public int PixelsPerSecond
{ {
get => _pixelsPerSecond; get => _pixelsPerSecond;
@ -113,12 +126,14 @@ namespace Artemis.UI.Shared.Services
var delta = CurrentTime - _lastUpdateTime; var delta = CurrentTime - _lastUpdateTime;
foreach (var folder in SelectedProfile.GetAllFolders()) foreach (var folder in SelectedProfile.GetAllFolders())
{ {
folder.CurrentTimeline = CurrentTimeline;
foreach (var baseLayerEffect in folder.LayerEffects) foreach (var baseLayerEffect in folder.LayerEffects)
baseLayerEffect.Update(delta.TotalSeconds); baseLayerEffect.Update(delta.TotalSeconds);
} }
foreach (var layer in SelectedProfile.GetAllLayers()) foreach (var layer in SelectedProfile.GetAllLayers())
{ {
layer.CurrentTimeline = CurrentTimeline;
layer.OverrideProgress(CurrentTime); layer.OverrideProgress(CurrentTime);
layer.LayerBrush?.Update(delta.TotalSeconds); layer.LayerBrush?.Update(delta.TotalSeconds);
foreach (var baseLayerEffect in layer.LayerEffects) foreach (var baseLayerEffect in layer.LayerEffects)
@ -212,6 +227,7 @@ namespace Artemis.UI.Shared.Services
public event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected; public event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;
public event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementUpdated; public event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementUpdated;
public event EventHandler CurrentTimeChanged; public event EventHandler CurrentTimeChanged;
public event EventHandler CurrentTimelineChanged;
public event EventHandler PixelsPerSecondChanged; public event EventHandler PixelsPerSecondChanged;
public event EventHandler ProfilePreviewUpdated; public event EventHandler ProfilePreviewUpdated;
@ -250,6 +266,11 @@ namespace Artemis.UI.Shared.Services
CurrentTimeChanged?.Invoke(this, EventArgs.Empty); CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
} }
protected virtual void OnCurrentTimelineChanged()
{
CurrentTimelineChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnPixelsPerSecondChanged() protected virtual void OnPixelsPerSecondChanged()
{ {
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);

View File

@ -148,13 +148,11 @@
HorizontalAlignment="Left"> HorizontalAlignment="Left">
<Grid> <Grid>
<StackPanel Visibility="{Binding RightStaticValue, Converter={StaticResource NullToVisibilityConverter}}" Orientation="Horizontal"> <StackPanel Visibility="{Binding RightStaticValue, Converter={StaticResource NullToVisibilityConverter}}" Orientation="Horizontal">
<TextBlock Margin="0 0 3 0" <TextBlock FontWeight="Light"
FontWeight="Light"
Text="{Binding SelectedLeftSideProperty.PropertyDescription.Prefix}" Text="{Binding SelectedLeftSideProperty.PropertyDescription.Prefix}"
Visibility="{Binding SelectedLeftSideProperty.PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"/> Visibility="{Binding SelectedLeftSideProperty.PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"/>
<TextBlock Text="{Binding RightStaticValue}"/> <TextBlock Text="{Binding RightStaticValue}"/>
<TextBlock Margin="3 0 0 0" <TextBlock FontWeight="Light"
FontWeight="Light"
Text="{Binding SelectedLeftSideProperty.PropertyDescription.Affix}" Text="{Binding SelectedLeftSideProperty.PropertyDescription.Affix}"
Visibility="{Binding SelectedLeftSideProperty.PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}" /> Visibility="{Binding SelectedLeftSideProperty.PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}" />
</StackPanel> </StackPanel>

View File

@ -9,27 +9,63 @@
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<Grid> <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="40" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4"> <TextBlock Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4">
Display conditions Display conditions
</TextBlock> </TextBlock>
<Separator Grid.Row="1" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" /> <Separator Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
<Grid Grid.Row="2"> <Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<ScrollViewer Margin="8" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <ScrollViewer Margin="8 0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl s:View.Model="{Binding RootGroup}" /> <ContentControl s:View.Model="{Binding RootGroup}" />
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
<StackPanel Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal"> <StackPanel Grid.Row="3" Grid.Column="0" Margin="10" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
<TextBlock Margin="10 0" VerticalAlignment="Center">Disabled</TextBlock> <ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="True" />
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" ToolTip="Default ToggleButton Style" /> <TextBlock Margin="5 0 0 0">Play once</TextBlock>
<TextBlock Margin="10 0 15 0" VerticalAlignment="Center">Enabled</TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">
<TextBlock>When conditions no longer met</TextBlock>
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding CurrentTimelineIndex}" Height="20" Margin="0 5 0 0">
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock>When conditions are no longer met, finish the timelines and then stop displaying.</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="PlayArrow" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11">WAIT FOR FINISH</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock>When conditions are no longer met, stop displaying immediately.</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="SkipNext" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11">SKIP</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -291,7 +291,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="0" Height="20" Margin="5 0 0 0"> <ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding CurrentTimelineIndex}" Height="20" Margin="5 0 0 0">
<ListBoxItem Padding="10 0"> <ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip> <ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5"> <ToolTip Placement="Top" VerticalOffset="-5">

View File

@ -84,6 +84,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
} }
} }
public int CurrentTimelineIndex
{
get => (int) ProfileEditorService.CurrentTimeline;
set
{
ProfileEditorService.CurrentTimeline = (Core.Models.Profile.LayerProperties.Timeline) value;
ProfileEditorService.CurrentTime = TimeSpan.Zero;
}
}
public bool PropertyTreeVisible => PropertyTreeIndex == 0; public bool PropertyTreeVisible => PropertyTreeIndex == 0;
public RenderProfileElement SelectedProfileElement public RenderProfileElement SelectedProfileElement
@ -130,6 +140,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.CurrentTimelineChanged += ProfileEditorServiceOnCurrentTimelineChanged;
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
base.OnInitialActivate(); base.OnInitialActivate();
@ -139,6 +150,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{ {
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.CurrentTimelineChanged -= ProfileEditorServiceOnCurrentTimelineChanged;
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
PopulateProperties(null); PopulateProperties(null);
@ -168,6 +180,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
NotifyOfPropertyChange(nameof(TimeCaretPosition)); NotifyOfPropertyChange(nameof(TimeCaretPosition));
} }
private void ProfileEditorServiceOnCurrentTimelineChanged(object? sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CurrentTimelineIndex));
TimelineViewModel.UpdateKeyframes();
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{ {
NotifyOfPropertyChange(nameof(TimeCaretPosition)); NotifyOfPropertyChange(nameof(TimeCaretPosition));

View File

@ -27,6 +27,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
var newKeyframe = new LayerPropertyKeyframe<T>( var newKeyframe = new LayerPropertyKeyframe<T>(
LayerPropertyKeyframe.Value, LayerPropertyKeyframe.Value,
LayerPropertyKeyframe.Position, LayerPropertyKeyframe.Position,
LayerPropertyKeyframe.Timeline,
LayerPropertyKeyframe.EasingFunction, LayerPropertyKeyframe.EasingFunction,
LayerPropertyKeyframe.LayerProperty LayerPropertyKeyframe.LayerProperty
); );

View File

@ -23,7 +23,7 @@
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
ItemsSource="{Binding TimelineKeyframeViewModels}" ItemsSource="{Binding TimelineKeyframeViewModels}"
Background="{DynamicResource MaterialDesignToolBarBackground}" Background="{DynamicResource MaterialDesignToolBarBackground}"
HorizontalAlignment="Left"> HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<Canvas /> <Canvas />
@ -49,20 +49,22 @@
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" Fill="{DynamicResource MaterialDesignDivider}" Height="1" /> <Rectangle Grid.Row="1" HorizontalAlignment="Stretch" Fill="{DynamicResource MaterialDesignDivider}" Height="1" />
<ItemsControl Grid.Row="2" <ItemsControl Grid.Row="2"
ItemsSource="{Binding LayerPropertyGroupViewModel.Children}"
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
ItemsSource="{Binding LayerPropertyGroupViewModel.Children}"> HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style >
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources> <ItemsControl.Resources>
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}"> <DataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}">
<ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}" <ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}" IsTabStop="False" HorizontalAlignment="Stretch"/>
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False" />
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}"> <DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
<ContentControl s:View.Model="{Binding TimelinePropertyBaseViewModel}" <ContentControl s:View.Model="{Binding TimelinePropertyBaseViewModel}" IsTabStop="False" HorizontalAlignment="Stretch" />
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False" />
</DataTemplate> </DataTemplate>
</ItemsControl.Resources> </ItemsControl.Resources>
</ItemsControl> </ItemsControl>

View File

@ -2,16 +2,19 @@
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Shared.Services.Interfaces;
using Stylet; using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{ {
public class TimelinePropertyGroupViewModel : PropertyChangedBase public class TimelinePropertyGroupViewModel : PropertyChangedBase
{ {
private readonly IProfileEditorService _profileEditorService;
private BindableCollection<double> _timelineKeyframeViewModels; private BindableCollection<double> _timelineKeyframeViewModels;
public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService)
{ {
_profileEditorService = profileEditorService;
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel; LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel;
TimelineKeyframeViewModels = new BindableCollection<double>(); TimelineKeyframeViewModels = new BindableCollection<double>();
@ -32,6 +35,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{ {
TimelineKeyframeViewModels.Clear(); TimelineKeyframeViewModels.Clear();
TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false) TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false)
.Where(k => k.Timeline == _profileEditorService.CurrentTimeline)
.Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds)); .Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds));
} }

View File

@ -9,7 +9,9 @@
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}" d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}"
Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"> Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
MinWidth="{Binding Width}"
HorizontalAlignment="Stretch">
<Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}"> <Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}">
<ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}" <ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}"
Background="{DynamicResource MaterialDesignToolBarBackground}" Background="{DynamicResource MaterialDesignToolBarBackground}"

View File

@ -28,7 +28,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
// Only show keyframes if they are enabled // Only show keyframes if they are enabled
if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled) if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled)
{ {
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList(); var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes
.Where(k => k.Timeline == _profileEditorService.CurrentTimeline)
.ToList();
var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList(); var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList();
TimelineKeyframeViewModels.RemoveRange(toRemove); TimelineKeyframeViewModels.RemoveRange(toRemove);
TimelineKeyframeViewModels.AddRange( TimelineKeyframeViewModels.AddRange(
@ -60,12 +62,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{ {
foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels)
timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond);
Width = TimelineKeyframeViewModels.Any() ? TimelineKeyframeViewModels.Max(t => t.X) + 25 : 0;
} }
} }
public abstract class TimelinePropertyViewModel : PropertyChangedBase, IDisposable public abstract class TimelinePropertyViewModel : PropertyChangedBase, IDisposable
{ {
private BindableCollection<TimelineKeyframeViewModel> _timelineKeyframeViewModels; private BindableCollection<TimelineKeyframeViewModel> _timelineKeyframeViewModels;
private double _width;
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
{ {
@ -81,6 +86,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
set => SetAndNotify(ref _timelineKeyframeViewModels, value); set => SetAndNotify(ref _timelineKeyframeViewModels, value);
} }
public double Width
{
get => _width;
set => SetAndNotify(ref _width, value);
}
public abstract void Dispose(); public abstract void Dispose();
public abstract void UpdateKeyframes(); public abstract void UpdateKeyframes();

View File

@ -12,7 +12,8 @@
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}" <Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
MouseDown="{s:Action TimelineCanvasMouseDown}" MouseDown="{s:Action TimelineCanvasMouseDown}"
MouseUp="{s:Action TimelineCanvasMouseUp}" MouseUp="{s:Action TimelineCanvasMouseUp}"
MouseMove="{s:Action TimelineCanvasMouseMove}"> MouseMove="{s:Action TimelineCanvasMouseMove}"
HorizontalAlignment="Stretch">
<Grid.Triggers> <Grid.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown"> <EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
<BeginStoryboard> <BeginStoryboard>
@ -31,12 +32,11 @@
</Grid.Triggers> </Grid.Triggers>
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}" <ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
Width="{Binding Width}"
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}" MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
HorizontalAlignment="Left"> HorizontalAlignment="Left">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" /> <ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}" HorizontalContentAlignment="Left" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>

View File

@ -18,7 +18,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
private readonly LayerPropertiesViewModel _layerPropertiesViewModel; private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private RectangleGeometry _selectionRectangle; private RectangleGeometry _selectionRectangle;
private double _width;
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups, public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
IProfileEditorService profileEditorService) IProfileEditorService profileEditorService)
@ -33,12 +32,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; } public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
public double Width
{
get => _width;
set => SetAndNotify(ref _width, value);
}
public RectangleGeometry SelectionRectangle public RectangleGeometry SelectionRectangle
{ {
get => _selectionRectangle; get => _selectionRectangle;

View File

@ -50,6 +50,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
LayerPropertyViewModel.LayerProperty.AddKeyframe(new LayerPropertyKeyframe<T>( LayerPropertyViewModel.LayerProperty.AddKeyframe(new LayerPropertyKeyframe<T>(
LayerPropertyViewModel.LayerProperty.CurrentValue, LayerPropertyViewModel.LayerProperty.CurrentValue,
_profileEditorService.CurrentTime, _profileEditorService.CurrentTime,
_profileEditorService.CurrentTimeline,
Easings.Functions.Linear, Easings.Functions.Linear,
LayerPropertyViewModel.LayerProperty LayerPropertyViewModel.LayerProperty
)); ));