1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +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.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();
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}));
}

View File

@ -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
{

View File

@ -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

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

@ -9,27 +9,63 @@
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="40" />
<RowDefinition Height="Auto" />
</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
</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">
<ScrollViewer Margin="8" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<ScrollViewer Margin="8 0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl s:View.Model="{Binding RootGroup}" />
</ScrollViewer>
</Grid>
<StackPanel Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock Margin="10 0" VerticalAlignment="Center">Disabled</TextBlock>
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" ToolTip="Default ToggleButton Style" />
<TextBlock Margin="10 0 15 0" VerticalAlignment="Center">Enabled</TextBlock>
<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.">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="True" />
<TextBlock Margin="5 0 0 0">Play once</TextBlock>
</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>
</UserControl>

View File

@ -291,7 +291,7 @@
<ColumnDefinition Width="Auto" />
</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.ToolTip>
<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 RenderProfileElement SelectedProfileElement
@ -130,6 +140,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.CurrentTimelineChanged += ProfileEditorServiceOnCurrentTimelineChanged;
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
base.OnInitialActivate();
@ -139,6 +150,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.CurrentTimelineChanged -= ProfileEditorServiceOnCurrentTimelineChanged;
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
PopulateProperties(null);
@ -168,6 +180,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
NotifyOfPropertyChange(nameof(TimeCaretPosition));
}
private void ProfileEditorServiceOnCurrentTimelineChanged(object? sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CurrentTimelineIndex));
TimelineViewModel.UpdateKeyframes();
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(TimeCaretPosition));

View File

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

View File

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

View File

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

View File

@ -9,7 +9,9 @@
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
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}">
<ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}"
Background="{DynamicResource MaterialDesignToolBarBackground}"

View File

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

View File

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

View File

@ -18,7 +18,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
private readonly IProfileEditorService _profileEditorService;
private RectangleGeometry _selectionRectangle;
private double _width;
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
IProfileEditorService profileEditorService)
@ -33,12 +32,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
public double Width
{
get => _width;
set => SetAndNotify(ref _width, value);
}
public RectangleGeometry 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.CurrentValue,
_profileEditorService.CurrentTime,
_profileEditorService.CurrentTimeline,
Easings.Functions.Linear,
LayerPropertyViewModel.LayerProperty
));