1
0
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:
SpoinkyNL 2020-07-16 23:15:49 +02:00
parent 7cfe9a46ee
commit dc8ca8ee95
17 changed files with 287 additions and 257 deletions

View File

@ -18,11 +18,6 @@ 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>
@ -33,11 +28,4 @@ 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,16 +23,10 @@ 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>
@ -90,7 +84,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 in the currently active timeline. /// or existing keyframe.
/// </param> /// </param>
public void SetCurrentValue(T value, TimeSpan? time) public void SetCurrentValue(T value, TimeSpan? time)
{ {
@ -99,10 +93,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 && k.Timeline == ProfileElement.CurrentTimeline); var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
// 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, ProfileElement.CurrentTimeline, Easings.Functions.Linear, this)); AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
else else
currentKeyframe.Value = value; currentKeyframe.Value = value;
@ -138,7 +132,6 @@ 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
); );
@ -200,43 +193,23 @@ 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 = keyframeSet.LastOrDefault(k => k.Position <= TimelineProgress); CurrentKeyframe = _keyframes.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 = keyframeSet.IndexOf(CurrentKeyframe) + 1; var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
NextKeyframe = keyframeSet.Count > nextIndex ? keyframeSet[nextIndex] : null; NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[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 = keyframeSet.Any() ? keyframeSet[0].Value : BaseValue; CurrentValue = _keyframes.Any() ? _keyframes[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
{ {
// If the current keyframe belongs to a previous timeline, consider it starting at 0 var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
var currentKeyframePosition = CurrentKeyframe.Position; var keyframeProgress = (float)((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
if (CurrentKeyframe.Timeline != ProfileElement.CurrentTimeline) var keyframeProgressEased = (float)Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
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); UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
} }
@ -254,15 +227,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
} }
/// <summary> /// <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> /// </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)
@ -289,8 +258,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
))); )));
} }
@ -319,8 +287,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,12 +6,10 @@ 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, 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; _position = position;
_timeline = timeline;
Value = value; Value = value;
LayerProperty = layerProperty; LayerProperty = layerProperty;
EasingFunction = easingFunction; EasingFunction = easingFunction;
@ -27,17 +25,6 @@ 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

@ -19,11 +19,6 @@ 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.
@ -71,6 +66,66 @@ namespace Artemis.Core.Models.Profile
#endregion #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 #region Effects
protected List<BaseLayerEffect> _layerEffects; protected List<BaseLayerEffect> _layerEffects;

View File

@ -1,7 +1,6 @@
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;
@ -15,7 +14,6 @@ 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; }
@ -56,11 +54,6 @@ 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

@ -25,7 +25,6 @@ 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)
{ {
@ -49,24 +48,15 @@ namespace Artemis.UI.Shared.Services
set set
{ {
if (_currentTime.Equals(value)) return; if (_currentTime.Equals(value)) return;
_currentTime = value; if (value > SelectedProfileElement.TimelineLength)
_currentTime = SelectedProfileElement.TimelineLength;
else
_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;
@ -126,14 +116,12 @@ 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)

View File

@ -1,5 +1,6 @@
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.Conditions; using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Models.Surface; using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract;

View File

@ -30,9 +30,10 @@
</ScrollViewer> </ScrollViewer>
</Grid> </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."> <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.">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="True" /> <CheckBox Style="{StaticResource MaterialDesignCheckBox}">
<TextBlock Margin="5 0 0 0">Play once</TextBlock> Play once
</CheckBox>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right"> <StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">

View File

@ -79,50 +79,49 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Misc controls & time display --> <!-- Misc controls & time display -->
<DockPanel Grid.Row="0" VerticalAlignment="Center"> <Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
<StackPanel Orientation="Horizontal"> <DockPanel VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" <StackPanel Orientation="Horizontal">
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}" <Button Style="{StaticResource MaterialDesignIconForegroundButton}"
Focusable="False"> ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
<materialDesign:PackIcon Kind="StepForward" /> Focusable="False">
</Button> <materialDesign:PackIcon Kind="StepForward" />
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" </Button>
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}"
<StackPanel> ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
<materialDesign:PackIcon Kind="Play" <StackPanel>
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" /> <materialDesign:PackIcon Kind="Play"
<materialDesign:PackIcon Kind="Pause" Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" /> <materialDesign:PackIcon Kind="Pause"
</StackPanel> Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</Button> </StackPanel>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" </Button>
Command="{s:Action GoToStart}" Focusable="False"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
<materialDesign:PackIcon Kind="SkipBackward" /> <materialDesign:PackIcon Kind="SkipBackward" />
</Button> </Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
Command="{s:Action GoToEnd}" Focusable="False"> <materialDesign:PackIcon Kind="SkipForward" />
<materialDesign:PackIcon Kind="SkipForward" /> </Button>
</Button> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" <materialDesign:PackIcon Kind="SkipPrevious" />
Command="{s:Action GoToPreviousFrame}" Focusable="False"> </Button>
<materialDesign:PackIcon Kind="SkipPrevious" /> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
</Button> <materialDesign:PackIcon Kind="SkipNext" />
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" </Button>
Command="{s:Action GoToNextFrame}" Focusable="False"> <ToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
<materialDesign:PackIcon Kind="SkipNext" /> ToolTip="Repeat after last keyframe"
</Button> IsChecked="{Binding RepeatAfterLastKeyframe}"
<ToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}" Focusable="False">
ToolTip="Repeat after last keyframe" <materialDesign:PackIcon Kind="Repeat" Height="24" Width="24" />
IsChecked="{Binding RepeatAfterLastKeyframe}" </ToggleButton>
Focusable="False"> </StackPanel>
<materialDesign:PackIcon Kind="Repeat" Height="24" Width="24" /> <StackPanel VerticalAlignment="Center">
</ToggleButton> <TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
</StackPanel> Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
<StackPanel VerticalAlignment="Center"> </StackPanel>
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" </DockPanel>
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" /> </Border>
</StackPanel>
</DockPanel>
<!-- Properties tree --> <!-- Properties tree -->
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1"> <materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
@ -135,7 +134,6 @@
<Grid> <Grid>
<ContentControl s:View.Model="{Binding TreeViewModel}" /> <ContentControl s:View.Model="{Binding TreeViewModel}" />
</Grid> </Grid>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
<materialDesign:TransitionerSlide> <materialDesign:TransitionerSlide>
@ -169,28 +167,58 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Timeline header --> <!-- Timeline headers -->
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" <ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
<Grid Background="{DynamicResource MaterialDesignCardBackground}"> <Grid Background="{DynamicResource MaterialDesignCardBackground}">
<!-- Caret --> <!-- 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}" Margin="{Binding TimeCaretPosition}"
Cursor="SizeWE" Cursor="SizeWE"
MouseDown="{s:Action TimelineMouseDown}" MouseDown="{s:Action TimelineMouseDown}"
MouseUp="{s:Action TimelineMouseUp}" MouseUp="{s:Action TimelineMouseUp}"
MouseMove="{s:Action TimelineMouseMove}"> MouseMove="{s:Action TimelineMouseMove}">
<Polygon Points="-10,0 0,20 10,0" Fill="{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}" <Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
</Canvas> </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}" Fill="{DynamicResource MaterialDesignBody}"
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}" PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}" HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}" VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
OffsetFirstValue="True"
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" /> 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> </Grid>
</ScrollViewer> </ScrollViewer>
@ -201,17 +229,18 @@
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
ScrollChanged="TimelineScrollChanged"> ScrollChanged="TimelineScrollChanged">
<Grid> <Grid Background="{DynamicResource MaterialDesignToolBarBackground}">
<Canvas Panel.ZIndex="1" <Canvas Grid.Column="0"
Panel.ZIndex="1"
Margin="{Binding TimeCaretPosition}" Margin="{Binding TimeCaretPosition}"
Cursor="SizeWE" Cursor="SizeWE"
MouseDown="{s:Action TimelineMouseDown}" MouseDown="{s:Action TimelineMouseDown}"
MouseUp="{s:Action TimelineMouseUp}" MouseUp="{s:Action TimelineMouseUp}"
MouseMove="{s:Action TimelineMouseMove}"> MouseMove="{s:Action TimelineMouseMove}">
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" <Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
</Canvas> </Canvas>
<ContentControl x:Name="PropertyTimeLine" s:View.Model="{Binding TimelineViewModel}" />
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" />
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
@ -291,59 +320,15 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </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 --> <!-- Zoom control -->
<Slider Grid.Column="1" <Slider Grid.Column="1"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="10 5" Margin="10 5"
Minimum="31" Minimum="100"
Maximum="350" Maximum="350"
TickFrequency="1"
IsSnapToTickEnabled="True"
Value="{Binding ProfileEditorService.PixelsPerSecond}" Value="{Binding ProfileEditorService.PixelsPerSecond}"
Width="319" /> Width="319" />

View File

@ -27,14 +27,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{ {
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private LayerPropertyGroupViewModel _brushPropertyGroup; private LayerPropertyGroupViewModel _brushPropertyGroup;
private bool _repeatAfterLastKeyframe;
private int _propertyTreeIndex;
private RenderProfileElement _selectedProfileElement;
private BindableCollection<LayerPropertyGroupViewModel> _layerPropertyGroups;
private TreeViewModel _treeViewModel;
private EffectsViewModel _effectsViewModel; private EffectsViewModel _effectsViewModel;
private TimelineViewModel _timelineViewModel; private BindableCollection<LayerPropertyGroupViewModel> _layerPropertyGroups;
private bool _playing; 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, public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService,
ILayerPropertyVmFactory layerPropertyVmFactory) 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 bool PropertyTreeVisible => PropertyTreeIndex == 0;
public RenderProfileElement SelectedProfileElement public RenderProfileElement SelectedProfileElement
@ -140,7 +130,6 @@ 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();
@ -150,7 +139,6 @@ 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);
@ -171,6 +159,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) 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); PopulateProperties(e.RenderProfileElement);
} }
@ -180,11 +176,6 @@ 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)
{ {
@ -239,6 +230,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
} }
TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups); TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups);
TimelineViewModel?.Dispose();
TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups); TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups);
ApplyLayerBrush(); ApplyLayerBrush();
@ -287,7 +280,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
} }
SortProperties(); SortProperties();
TimelineViewModel.UpdateKeyframes(); UpdateKeyframes();
} }
private void ApplyEffects() private void ApplyEffects()
@ -322,7 +315,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
} }
SortProperties(); SortProperties();
TimelineViewModel.UpdateKeyframes(); UpdateKeyframes();
} }
private void SortProperties() private void SortProperties()
@ -347,6 +340,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
} }
} }
private void UpdateKeyframes()
{
TimelineViewModel.Update();
}
#endregion #endregion
#region Drag and drop #region Drag and drop

View File

@ -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), public static readonly DependencyProperty VisibleWidthProperty = DependencyProperty.Register(nameof(VisibleWidth), typeof(double), typeof(PropertyTimelineHeader),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender)); 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 _subd1;
private double _subd2; private double _subd2;
private double _subd3; private double _subd3;
@ -57,6 +60,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
set => SetValue(VisibleWidthProperty, value); set => SetValue(VisibleWidthProperty, value);
} }
public bool OffsetFirstValue
{
get => (bool) GetValue(OffsetFirstValueProperty);
set => SetValue(OffsetFirstValueProperty, value);
}
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)
{ {
base.OnRender(drawingContext); base.OnRender(drawingContext);
@ -73,7 +82,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
var count = (width + offsetUnits) / units; var count = (width + offsetUnits) / units;
for (var i = 0; i < count; i++) 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 // Add a 100px margin to allow the text to partially render when needed
if (x < HorizontalOffset - 100 || x > HorizontalOffset + width) if (x < HorizontalOffset - 100 || x > HorizontalOffset + width)
continue; continue;
@ -95,8 +104,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
count = (width + offsetUnits) / units; count = (width + offsetUnits) / units;
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
var x = i * units - offsetUnits + 1; var x = i * units - offsetUnits;
if (x > HorizontalOffset && x < HorizontalOffset + width) 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)); 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++) for (var i = 0; i < count; i++)
{ {
if (Math.Abs(i % mul) < 0.001) continue; 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) if (x > HorizontalOffset && x < HorizontalOffset + width)
drawingContext.DrawLine(linePen, new Point(x, 25), new Point(x, 30)); 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 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); var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, 9, Fill, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
if (x == 1) if (x == 0 && OffsetFirstValue)
drawingContext.DrawText(formattedText, new Point(x, 2)); drawingContext.DrawText(formattedText, new Point(2, 2));
else else
drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 2)); drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 2));
} }

View File

@ -27,7 +27,6 @@ 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

@ -35,7 +35,6 @@ 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

@ -28,9 +28,7 @@ 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 var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
.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(

View File

@ -9,11 +9,11 @@
d:DesignHeight="25" d:DesignHeight="25"
d:DesignWidth="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:TimelineViewModel}"> d:DataContext="{d:DesignInstance local:TimelineViewModel}">
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}" <Grid x:Name="TimelineContainerGrid"
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>
@ -41,6 +41,29 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </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 --> <!-- Multi-selection rectangle -->
<Path x:Name="MultiSelectionPath" <Path x:Name="MultiSelectionPath"
Data="{Binding SelectionRectangle}" Data="{Binding SelectionRectangle}"

View File

@ -13,21 +13,23 @@ using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{ {
public class TimelineViewModel : Screen public class TimelineViewModel : PropertyChangedBase, IViewAware, IDisposable
{ {
private readonly LayerPropertiesViewModel _layerPropertiesViewModel; private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private RectangleGeometry _selectionRectangle; private RectangleGeometry _selectionRectangle;
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups, public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups, IProfileEditorService profileEditorService)
IProfileEditorService profileEditorService)
{ {
_layerPropertiesViewModel = layerPropertiesViewModel; _layerPropertiesViewModel = layerPropertiesViewModel;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
LayerPropertyGroups = layerPropertyGroups; LayerPropertyGroups = layerPropertyGroups;
SelectionRectangle = new RectangleGeometry(); SelectionRectangle = new RectangleGeometry();
UpdateKeyframes(); _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
Update();
} }
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; } public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
@ -38,7 +40,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
set => SetAndNotify(ref _selectionRectangle, value); 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) 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 #region Command handlers
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
@ -274,5 +298,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
} }
#endregion #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
} }
} }

View File

@ -50,7 +50,6 @@ 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
)); ));