mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor - Changed approach to one timeline split into segments
This commit is contained in:
parent
7cfe9a46ee
commit
dc8ca8ee95
@ -18,11 +18,6 @@ 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>
|
||||
@ -33,11 +28,4 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// </summary>
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
}
|
||||
|
||||
public enum Timeline
|
||||
{
|
||||
Start,
|
||||
Main,
|
||||
End
|
||||
}
|
||||
}
|
||||
@ -23,16 +23,10 @@ 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>
|
||||
@ -90,7 +84,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 in the currently active timeline.
|
||||
/// or existing keyframe.
|
||||
/// </param>
|
||||
public void SetCurrentValue(T value, TimeSpan? time)
|
||||
{
|
||||
@ -99,10 +93,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
else
|
||||
{
|
||||
// If on a keyframe, update the keyframe
|
||||
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value && k.Timeline == ProfileElement.CurrentTimeline);
|
||||
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
|
||||
// Create a new keyframe if none found
|
||||
if (currentKeyframe == null)
|
||||
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, ProfileElement.CurrentTimeline, Easings.Functions.Linear, this));
|
||||
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
|
||||
else
|
||||
currentKeyframe.Value = value;
|
||||
|
||||
@ -138,7 +132,6 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
var newKeyframe = new LayerPropertyKeyframe<T>(
|
||||
keyframe.Value,
|
||||
keyframe.Position,
|
||||
keyframe.Timeline,
|
||||
keyframe.EasingFunction,
|
||||
keyframe.LayerProperty
|
||||
);
|
||||
@ -200,43 +193,23 @@ 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 = 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();
|
||||
|
||||
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress);
|
||||
// Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current
|
||||
var nextIndex = keyframeSet.IndexOf(CurrentKeyframe) + 1;
|
||||
NextKeyframe = keyframeSet.Count > nextIndex ? keyframeSet[nextIndex] : null;
|
||||
var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
|
||||
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
|
||||
|
||||
// No need to update the current value if either of the keyframes are null
|
||||
if (CurrentKeyframe == null)
|
||||
CurrentValue = keyframeSet.Any() ? keyframeSet[0].Value : BaseValue;
|
||||
CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue;
|
||||
else if (NextKeyframe == null)
|
||||
CurrentValue = CurrentKeyframe.Value;
|
||||
// Only determine progress and current value if both keyframes are present
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
||||
var keyframeProgress = (float)((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
||||
var keyframeProgressEased = (float)Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
|
||||
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
|
||||
}
|
||||
|
||||
@ -254,15 +227,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the keyframes in ascending order by position and divides the keyframes into different timelines
|
||||
/// Sorts the keyframes in ascending order by position
|
||||
/// </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)
|
||||
@ -289,8 +258,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,
|
||||
(Easings.Functions)k.EasingFunction,
|
||||
this
|
||||
)));
|
||||
}
|
||||
@ -319,8 +287,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
Value = JsonConvert.SerializeObject(k.Value),
|
||||
Position = k.Position,
|
||||
Timeline = (int) k.Timeline,
|
||||
EasingFunction = (int) k.EasingFunction
|
||||
EasingFunction = (int)k.EasingFunction
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,12 +6,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
public class LayerPropertyKeyframe<T> : BaseLayerPropertyKeyframe
|
||||
{
|
||||
private TimeSpan _position;
|
||||
private Timeline _timeline;
|
||||
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Timeline timeline, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
|
||||
{
|
||||
_position = position;
|
||||
_timeline = timeline;
|
||||
Value = value;
|
||||
LayerProperty = layerProperty;
|
||||
EasingFunction = easingFunction;
|
||||
@ -27,17 +25,6 @@ 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
|
||||
{
|
||||
|
||||
@ -19,11 +19,6 @@ 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.
|
||||
@ -71,6 +66,66 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
#endregion
|
||||
|
||||
#region Timeline
|
||||
|
||||
private TimeSpan _startSegmentLength;
|
||||
private TimeSpan _mainSegmentLength;
|
||||
private TimeSpan _endSegmentLength;
|
||||
private bool _repeatMainSegment;
|
||||
private bool _alwaysFinishTimeline;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the start segment
|
||||
/// </summary>
|
||||
public TimeSpan StartSegmentLength
|
||||
{
|
||||
get => _startSegmentLength;
|
||||
set => SetAndNotify(ref _startSegmentLength, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the main segment
|
||||
/// </summary>
|
||||
public TimeSpan MainSegmentLength
|
||||
{
|
||||
get => _mainSegmentLength;
|
||||
set => SetAndNotify(ref _mainSegmentLength, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the end segment
|
||||
/// </summary>
|
||||
public TimeSpan EndSegmentLength
|
||||
{
|
||||
get => _endSegmentLength;
|
||||
set => SetAndNotify(ref _endSegmentLength, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total combined length of all three segments
|
||||
/// </summary>
|
||||
public TimeSpan TimelineLength => StartSegmentLength + MainSegmentLength + EndSegmentLength;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether main timeline should repeat itself as long as display conditions are met
|
||||
/// </summary>
|
||||
public bool RepeatMainSegment
|
||||
{
|
||||
get => _repeatMainSegment;
|
||||
set => SetAndNotify(ref _repeatMainSegment, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the timeline should finish when conditions are no longer met
|
||||
/// </summary>
|
||||
public bool AlwaysFinishTimeline
|
||||
{
|
||||
get => _alwaysFinishTimeline;
|
||||
set => SetAndNotify(ref _alwaysFinishTimeline, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Effects
|
||||
|
||||
protected List<BaseLayerEffect> _layerEffects;
|
||||
@ -138,7 +193,7 @@ namespace Artemis.Core.Models.Profile
|
||||
#region Conditions
|
||||
|
||||
private DisplayConditionGroup _displayConditionGroup;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the root display condition group
|
||||
/// </summary>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
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;
|
||||
@ -15,7 +14,6 @@ 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; }
|
||||
@ -55,12 +53,7 @@ namespace Artemis.UI.Shared.Services.Interfaces
|
||||
/// Occurs when the current editor time is changed
|
||||
/// </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>
|
||||
|
||||
@ -25,7 +25,6 @@ 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)
|
||||
{
|
||||
@ -49,24 +48,15 @@ namespace Artemis.UI.Shared.Services
|
||||
set
|
||||
{
|
||||
if (_currentTime.Equals(value)) return;
|
||||
_currentTime = value;
|
||||
if (value > SelectedProfileElement.TimelineLength)
|
||||
_currentTime = SelectedProfileElement.TimelineLength;
|
||||
else
|
||||
_currentTime = value;
|
||||
UpdateProfilePreview();
|
||||
OnCurrentTimeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Timeline CurrentTimeline
|
||||
{
|
||||
get => _currentTimeline;
|
||||
set
|
||||
{
|
||||
if (_currentTimeline.Equals(value)) return;
|
||||
_currentTimeline = value;
|
||||
UpdateProfilePreview();
|
||||
OnCurrentTimelineChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int PixelsPerSecond
|
||||
{
|
||||
get => _pixelsPerSecond;
|
||||
@ -126,14 +116,12 @@ 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)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.Conditions;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
|
||||
@ -30,9 +30,10 @@
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<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 Grid.Row="3" Grid.Column="0" Margin="13" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
|
||||
<CheckBox Style="{StaticResource MaterialDesignCheckBox}">
|
||||
Play once
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">
|
||||
|
||||
@ -79,50 +79,49 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Misc controls & time display -->
|
||||
<DockPanel Grid.Row="0" VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="StepForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
|
||||
<StackPanel>
|
||||
<materialDesign:PackIcon Kind="Play"
|
||||
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="Pause"
|
||||
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start"
|
||||
Command="{s:Action GoToStart}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipBackward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end"
|
||||
Command="{s:Action GoToEnd}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame"
|
||||
Command="{s:Action GoToPreviousFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipPrevious" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame"
|
||||
Command="{s:Action GoToNextFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipNext" />
|
||||
</Button>
|
||||
<ToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||
ToolTip="Repeat after last keyframe"
|
||||
IsChecked="{Binding RepeatAfterLastKeyframe}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="Repeat" Height="24" Width="24" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
|
||||
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="StepForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
|
||||
<StackPanel>
|
||||
<materialDesign:PackIcon Kind="Play"
|
||||
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="Pause"
|
||||
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipBackward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipPrevious" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipNext" />
|
||||
</Button>
|
||||
<ToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||
ToolTip="Repeat after last keyframe"
|
||||
IsChecked="{Binding RepeatAfterLastKeyframe}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="Repeat" Height="24" Width="24" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
|
||||
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- Properties tree -->
|
||||
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
|
||||
@ -135,7 +134,6 @@
|
||||
<Grid>
|
||||
<ContentControl s:View.Model="{Binding TreeViewModel}" />
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<materialDesign:TransitionerSlide>
|
||||
@ -169,28 +167,58 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Timeline header -->
|
||||
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||
<!-- Timeline headers -->
|
||||
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||
<Grid Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Caret -->
|
||||
<Canvas Panel.ZIndex="1"
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding TimelineViewModel.StartSegmentWidth}" />
|
||||
<ColumnDefinition Width="{Binding TimelineViewModel.MainSegmentWidth}" />
|
||||
<ColumnDefinition Width="{Binding TimelineViewModel.EndSegmentWidth}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Canvas Grid.ColumnSpan="3"
|
||||
Panel.ZIndex="2"
|
||||
Margin="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}">
|
||||
<Polygon Points="-10,0 0,20 10,0" Fill="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
|
||||
StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
<Polygon Points="-8,0 -8,8 0,20, 8,8 8,0" Fill="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
<!-- Time -->
|
||||
<timeline:PropertyTimelineHeader Margin="0 25 0 0"
|
||||
|
||||
<timeline:PropertyTimelineHeader Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0 25 0 0"
|
||||
Fill="{DynamicResource MaterialDesignBody}"
|
||||
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
|
||||
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
|
||||
OffsetFirstValue="True"
|
||||
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
|
||||
|
||||
<!-- Start segment -->
|
||||
<TextBlock Grid.Column="0" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
|
||||
Start
|
||||
</TextBlock>
|
||||
|
||||
<!-- Main segment -->
|
||||
<TextBlock Grid.Column="1" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
|
||||
Main
|
||||
</TextBlock>
|
||||
|
||||
<!-- End segment -->
|
||||
<TextBlock Grid.Column="2" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
|
||||
End
|
||||
</TextBlock>
|
||||
|
||||
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Cursor="SizeWE" />
|
||||
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Cursor="SizeWE" />
|
||||
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Cursor="SizeWE" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@ -201,17 +229,18 @@
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Grid>
|
||||
<Canvas Panel.ZIndex="1"
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}">
|
||||
<Canvas Grid.Column="0"
|
||||
Panel.ZIndex="1"
|
||||
Margin="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}">
|
||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
|
||||
StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
<ContentControl x:Name="PropertyTimeLine" s:View.Model="{Binding TimelineViewModel}" />
|
||||
|
||||
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
@ -291,59 +320,15 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<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">
|
||||
<StackPanel>
|
||||
<TextBlock>Select the <Run FontWeight="Bold">enter</Run> timeline</TextBlock>
|
||||
<TextBlock>Played when the folder/layer starts displaying (condition is met)</TextBlock>
|
||||
</StackPanel>
|
||||
</ToolTip>
|
||||
</ListBoxItem.ToolTip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="RayStart" Width="20" Height="20" Margin="0 -4" />
|
||||
<TextBlock Margin="5 0 0 0" FontSize="11">ENTER</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem Padding="10 0">
|
||||
<ListBoxItem.ToolTip>
|
||||
<ToolTip Placement="Top" VerticalOffset="-5">
|
||||
<StackPanel>
|
||||
<TextBlock>Select the <Run FontWeight="Bold">main</Run> timeline</TextBlock>
|
||||
<TextBlock>Played after the enter timeline finishes, either on repeat or once</TextBlock>
|
||||
</StackPanel>
|
||||
</ToolTip>
|
||||
</ListBoxItem.ToolTip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="RayStartEnd" Width="20" Height="20" Margin="0 -4" />
|
||||
<TextBlock Margin="5 0 0 0" FontSize="11">MAIN</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem Padding="10 0">
|
||||
<ListBoxItem.ToolTip>
|
||||
<ToolTip Placement="Top" VerticalOffset="-5">
|
||||
<StackPanel>
|
||||
<TextBlock>Select the <Run FontWeight="Bold">exit</Run> timeline</TextBlock>
|
||||
<TextBlock>Played when the folder/layer stops displaying (conditon no longer met)</TextBlock>
|
||||
</StackPanel>
|
||||
</ToolTip>
|
||||
|
||||
</ListBoxItem.ToolTip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="RayEnd" Width="20" Height="20" Margin="0 -4" />
|
||||
<TextBlock Margin="5 0 0 0" FontSize="11">EXIT</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
</ListBox>
|
||||
|
||||
<!-- Zoom control -->
|
||||
<Slider Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="10 5"
|
||||
Minimum="31"
|
||||
Minimum="100"
|
||||
Maximum="350"
|
||||
TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
Value="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
Width="319" />
|
||||
|
||||
|
||||
@ -27,14 +27,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||
private LayerPropertyGroupViewModel _brushPropertyGroup;
|
||||
private bool _repeatAfterLastKeyframe;
|
||||
private int _propertyTreeIndex;
|
||||
private RenderProfileElement _selectedProfileElement;
|
||||
private BindableCollection<LayerPropertyGroupViewModel> _layerPropertyGroups;
|
||||
private TreeViewModel _treeViewModel;
|
||||
private EffectsViewModel _effectsViewModel;
|
||||
private TimelineViewModel _timelineViewModel;
|
||||
private BindableCollection<LayerPropertyGroupViewModel> _layerPropertyGroups;
|
||||
private bool _playing;
|
||||
private int _propertyTreeIndex;
|
||||
private bool _repeatAfterLastKeyframe;
|
||||
private RenderProfileElement _selectedProfileElement;
|
||||
private TimelineViewModel _timelineViewModel;
|
||||
private TreeViewModel _treeViewModel;
|
||||
|
||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService,
|
||||
ILayerPropertyVmFactory layerPropertyVmFactory)
|
||||
@ -84,16 +84,6 @@ 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
|
||||
@ -133,14 +123,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
get => _timelineViewModel;
|
||||
set => SetAndNotify(ref _timelineViewModel, value);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
PopulateProperties(ProfileEditorService.SelectedProfileElement);
|
||||
|
||||
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
|
||||
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
||||
ProfileEditorService.CurrentTimelineChanged += ProfileEditorServiceOnCurrentTimelineChanged;
|
||||
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
|
||||
base.OnInitialActivate();
|
||||
@ -150,7 +139,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
|
||||
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
|
||||
ProfileEditorService.CurrentTimelineChanged -= ProfileEditorServiceOnCurrentTimelineChanged;
|
||||
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
|
||||
PopulateProperties(null);
|
||||
@ -171,6 +159,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
|
||||
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
||||
{
|
||||
// Placeholder
|
||||
if (e.RenderProfileElement != null)
|
||||
{
|
||||
e.RenderProfileElement.StartSegmentLength = TimeSpan.FromMilliseconds(1000);
|
||||
e.RenderProfileElement.MainSegmentLength = TimeSpan.FromMilliseconds(5000);
|
||||
e.RenderProfileElement.EndSegmentLength = TimeSpan.FromMilliseconds(1000);
|
||||
}
|
||||
|
||||
PopulateProperties(e.RenderProfileElement);
|
||||
}
|
||||
|
||||
@ -180,11 +176,6 @@ 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)
|
||||
{
|
||||
@ -239,6 +230,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
}
|
||||
|
||||
TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups);
|
||||
|
||||
TimelineViewModel?.Dispose();
|
||||
TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups);
|
||||
|
||||
ApplyLayerBrush();
|
||||
@ -287,7 +280,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
}
|
||||
|
||||
SortProperties();
|
||||
TimelineViewModel.UpdateKeyframes();
|
||||
UpdateKeyframes();
|
||||
}
|
||||
|
||||
private void ApplyEffects()
|
||||
@ -322,7 +315,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
}
|
||||
|
||||
SortProperties();
|
||||
TimelineViewModel.UpdateKeyframes();
|
||||
UpdateKeyframes();
|
||||
}
|
||||
|
||||
private void SortProperties()
|
||||
@ -347,6 +340,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateKeyframes()
|
||||
{
|
||||
TimelineViewModel.Update();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drag and drop
|
||||
|
||||
@ -23,6 +23,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
public static readonly DependencyProperty VisibleWidthProperty = DependencyProperty.Register(nameof(VisibleWidth), typeof(double), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty OffsetFirstValueProperty = DependencyProperty.Register(nameof(OffsetFirstValue), typeof(bool), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
private double _subd1;
|
||||
private double _subd2;
|
||||
private double _subd3;
|
||||
@ -57,6 +60,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
set => SetValue(VisibleWidthProperty, value);
|
||||
}
|
||||
|
||||
public bool OffsetFirstValue
|
||||
{
|
||||
get => (bool) GetValue(OffsetFirstValueProperty);
|
||||
set => SetValue(OffsetFirstValueProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
base.OnRender(drawingContext);
|
||||
@ -73,7 +82,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
var count = (width + offsetUnits) / units;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var x = i * units - offsetUnits + 1;
|
||||
var x = i * units - offsetUnits;
|
||||
// Add a 100px margin to allow the text to partially render when needed
|
||||
if (x < HorizontalOffset - 100 || x > HorizontalOffset + width)
|
||||
continue;
|
||||
@ -95,8 +104,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
count = (width + offsetUnits) / units;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var x = i * units - offsetUnits + 1;
|
||||
if (x > HorizontalOffset && x < HorizontalOffset + width)
|
||||
var x = i * units - offsetUnits;
|
||||
if (x == 0 && OffsetFirstValue)
|
||||
drawingContext.DrawLine(linePen, new Point(1, 20), new Point(1, 30));
|
||||
else if (x > HorizontalOffset && x < HorizontalOffset + width)
|
||||
drawingContext.DrawLine(linePen, new Point(x, 20), new Point(x, 30));
|
||||
}
|
||||
|
||||
@ -107,7 +118,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (Math.Abs(i % mul) < 0.001) continue;
|
||||
var x = i * units - offsetUnits + 1;
|
||||
var x = i * units - offsetUnits;
|
||||
if (x > HorizontalOffset && x < HorizontalOffset + width)
|
||||
drawingContext.DrawLine(linePen, new Point(x, 25), new Point(x, 30));
|
||||
}
|
||||
@ -117,8 +128,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
var typeFace = new Typeface(FontFamily, new FontStyle(), new FontWeight(), new FontStretch());
|
||||
var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, 9, Fill, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||
if (x == 1)
|
||||
drawingContext.DrawText(formattedText, new Point(x, 2));
|
||||
if (x == 0 && OffsetFirstValue)
|
||||
drawingContext.DrawText(formattedText, new Point(2, 2));
|
||||
else
|
||||
drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 2));
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
var newKeyframe = new LayerPropertyKeyframe<T>(
|
||||
LayerPropertyKeyframe.Value,
|
||||
LayerPropertyKeyframe.Position,
|
||||
LayerPropertyKeyframe.Timeline,
|
||||
LayerPropertyKeyframe.EasingFunction,
|
||||
LayerPropertyKeyframe.LayerProperty
|
||||
);
|
||||
|
||||
@ -35,7 +35,6 @@ 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));
|
||||
}
|
||||
|
||||
|
||||
@ -28,9 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
// Only show keyframes if they are enabled
|
||||
if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled)
|
||||
{
|
||||
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes
|
||||
.Where(k => k.Timeline == _profileEditorService.CurrentTimeline)
|
||||
.ToList();
|
||||
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
|
||||
var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList();
|
||||
TimelineKeyframeViewModels.RemoveRange(toRemove);
|
||||
TimelineKeyframeViewModels.AddRange(
|
||||
|
||||
@ -9,11 +9,11 @@
|
||||
d:DesignHeight="25"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:TimelineViewModel}">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
<Grid x:Name="TimelineContainerGrid"
|
||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||
MouseMove="{s:Action TimelineCanvasMouseMove}"
|
||||
HorizontalAlignment="Stretch">
|
||||
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
@ -41,6 +41,29 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
||||
Opacity="0.5"
|
||||
StrokeDashArray="4 2"
|
||||
X1="{Binding StartSegmentEndPosition}"
|
||||
X2="{Binding StartSegmentEndPosition}"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
|
||||
HorizontalAlignment="Left" />
|
||||
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
||||
Opacity="0.5"
|
||||
StrokeDashArray="4 2"
|
||||
X1="{Binding MainSegmentEndPosition}"
|
||||
X2="{Binding MainSegmentEndPosition}"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
|
||||
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
||||
Opacity="0.5"
|
||||
StrokeDashArray="4 2"
|
||||
X1="{Binding EndSegmentEndPosition}"
|
||||
X2="{Binding EndSegmentEndPosition}"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path x:Name="MultiSelectionPath"
|
||||
Data="{Binding SelectionRectangle}"
|
||||
|
||||
@ -13,21 +13,23 @@ using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelineViewModel : Screen
|
||||
public class TimelineViewModel : PropertyChangedBase, IViewAware, IDisposable
|
||||
{
|
||||
private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private RectangleGeometry _selectionRectangle;
|
||||
|
||||
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
|
||||
IProfileEditorService profileEditorService)
|
||||
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_layerPropertiesViewModel = layerPropertiesViewModel;
|
||||
_profileEditorService = profileEditorService;
|
||||
|
||||
LayerPropertyGroups = layerPropertyGroups;
|
||||
SelectionRectangle = new RectangleGeometry();
|
||||
|
||||
UpdateKeyframes();
|
||||
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
||||
@ -38,7 +40,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
set => SetAndNotify(ref _selectionRectangle, value);
|
||||
}
|
||||
|
||||
public void UpdateKeyframes()
|
||||
public double StartSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.StartSegmentLength.TotalSeconds ?? 0;
|
||||
public double StartSegmentEndPosition => StartSegmentWidth;
|
||||
public double MainSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.MainSegmentLength.TotalSeconds ?? 0;
|
||||
public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth;
|
||||
public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0;
|
||||
public double EndSegmentEndPosition => StartSegmentWidth + MainSegmentWidth + EndSegmentWidth;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
|
||||
{
|
||||
@ -52,6 +66,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(StartSegmentWidth));
|
||||
NotifyOfPropertyChange(nameof(StartSegmentEndPosition));
|
||||
NotifyOfPropertyChange(nameof(MainSegmentWidth));
|
||||
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
|
||||
NotifyOfPropertyChange(nameof(EndSegmentWidth));
|
||||
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
||||
}
|
||||
|
||||
#region Command handlers
|
||||
|
||||
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
|
||||
@ -274,5 +298,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IViewAware
|
||||
|
||||
public void AttachView(UIElement view)
|
||||
{
|
||||
if (View != null)
|
||||
throw new InvalidOperationException(string.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, GetType().Name));
|
||||
|
||||
View = view;
|
||||
}
|
||||
|
||||
public UIElement View { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,6 @@ 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
|
||||
));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user