Implemented keyframe easing

This commit is contained in:
SpoinkyNL committed 2020-01-12 23:49:10 +01:00
1 parent 1a96707e5b
commit ac7e0d4f78
33 files changed
+651 -164

No files matched your search

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Services.Interfaces;
using Stylet;
@@ -9,10 +10,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public class PropertyTimelineViewModel : PropertyChangedBase
{
private readonly IProfileEditorService _profileEditorService;
private readonly IPropertyTrackViewModelFactory _propertyTrackViewModelFactory;
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel,
IProfileEditorService profileEditorService,
IPropertyTrackViewModelFactory propertyTrackViewModelFactory)
{
_profileEditorService = profileEditorService;
_propertyTrackViewModelFactory = propertyTrackViewModelFactory;
LayerPropertiesViewModel = layerPropertiesViewModel;
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
@@ -50,7 +55,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
private void CreateViewModels(LayerPropertyViewModel property)
{
PropertyTrackViewModels.Add(new PropertyTrackViewModel(this, property));
PropertyTrackViewModels.Add(_propertyTrackViewModelFactory.Create(this, property));
foreach (var child in property.Children)
CreateViewModels(child);
}
@@ -0,0 +1,51 @@
using System.Windows;
using System.Windows.Media;
using Artemis.Core.Utilities;
using Humanizer;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTrackEasingViewModel : PropertyChangedBase
{
private readonly PropertyTrackKeyframeViewModel _keyframeViewModel;
private bool _isEasingModeSelected;
public PropertyTrackEasingViewModel(PropertyTrackKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction)
{
_keyframeViewModel = keyframeViewModel;
_isEasingModeSelected = keyframeViewModel.Keyframe.EasingFunction == easingFunction;
EasingFunction = easingFunction;
Description = easingFunction.Humanize();
CreateGeometry();
}
public Easings.Functions EasingFunction { get; }
public PointCollection EasingPoints { get; set; }
public string Description { get; set; }
public bool IsEasingModeSelected
{
get => _isEasingModeSelected;
set
{
_isEasingModeSelected = value;
if (_isEasingModeSelected)
_keyframeViewModel.SelectEasingMode(this);
}
}
private void CreateGeometry()
{
EasingPoints = new PointCollection();
for (var i = 1; i <= 10; i++)
{
var x = i;
var y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
EasingPoints.Add(new Point(x, y));
}
}
}
}
@@ -1,25 +1,114 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Utilities;
using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTrackKeyframeViewModel : PropertyChangedBase
{
public PropertyTrackKeyframeViewModel(BaseKeyframe keyframe)
private readonly IProfileEditorService _profileEditorService;
private int _pixelsPerSecond;
public PropertyTrackKeyframeViewModel(BaseKeyframe keyframe, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
Keyframe = keyframe;
EasingViewModels = new BindableCollection<PropertyTrackEasingViewModel>();
CreateEasingViewModels();
}
public BaseKeyframe Keyframe { get; }
public BindableCollection<PropertyTrackEasingViewModel> EasingViewModels { get; set; }
public double X { get; set; }
public string Timestamp { get; set; }
public UIElement ParentView { get; set; }
public void Update(int pixelsPerSecond)
{
_pixelsPerSecond = pixelsPerSecond;
X = pixelsPerSecond * Keyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(Keyframe.Position.TotalSeconds):00}.{Keyframe.Position.Milliseconds:000}";
}
#region Keyframe movement
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
{
((IInputElement) sender).CaptureMouse();
}
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
{
((IInputElement) sender).ReleaseMouseCapture();
}
public void KeyframeMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
// Get the parent grid, need that for our position
var x = Math.Max(0, e.GetPosition(ParentView).X);
var newTime = TimeSpan.FromSeconds(x / _pixelsPerSecond);
// Round the time to something that fits the current zoom level
if (_pixelsPerSecond < 200)
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
else if (_pixelsPerSecond < 500)
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
else
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
{
Keyframe.Position = newTime;
Update(_pixelsPerSecond);
_profileEditorService.UpdateSelectedProfileElement();
return;
}
// If shift is held, snap to the current time
// Take a tolerance of 5 pixels (half a keyframe width)
var tolerance = 1000f / _pixelsPerSecond * 5;
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance)
Keyframe.Position = _profileEditorService.CurrentTime;
else
Keyframe.Position = newTime;
Update(_pixelsPerSecond);
_profileEditorService.UpdateSelectedProfileElement();
}
}
#endregion
#region Easing
private void CreateEasingViewModels()
{
foreach (Easings.Functions value in Enum.GetValues(typeof(Easings.Functions)))
EasingViewModels.Add(new PropertyTrackEasingViewModel(this, value));
}
public void SelectEasingMode(PropertyTrackEasingViewModel easingViewModel)
{
Keyframe.EasingFunction = easingViewModel.EasingFunction;
// Set every selection to false except on the VM that made the change
foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
propertyTrackEasingViewModel.IsEasingModeSelected = false;
_profileEditorService.UpdateSelectedProfileElement();
}
#endregion
}
}
@@ -28,7 +28,48 @@
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}" Width="10" Height="10" Margin="-5,6,0,0" ToolTip="{Binding Timestamp}" />
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
Width="10"
Height="10"
Margin="-5,6,0,0"
ToolTip="{Binding Timestamp}"
s:View.ActionTarget="{Binding}"
MouseDown="{s:Action KeyframeMouseDown}"
MouseUp="{s:Action KeyframeMouseUp}"
MouseMove="{s:Action KeyframeMouseMove}">
<Ellipse.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" />
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Path=IsEasingModeSelected, Mode=TwoWay}" />
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
StrokeThickness="1"
Points="{Binding EasingPoints}"
Stretch="Uniform"
Width="20"
Height="20"
Margin="0 0 10 0" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<!-- <MenuItem Header="Easing mode" IsEnabled="{Binding CanSelectEasingMode}"> -->
<!-- <MenuItem Header="Ease in" Command="{s:Action SetEasingMode}" CommandParameter="EaseIn" /> -->
<!-- <MenuItem Header="Ease out" Command="{s:Action SetEasingMode}" CommandParameter="EaseOut" /> -->
<!-- <MenuItem Header="Ease in and out" Command="{s:Action SetEasingMode}" CommandParameter="EaseInOut" /> -->
<!-- </MenuItem> -->
</ContextMenu>
</Ellipse.ContextMenu>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -1,12 +1,18 @@
using System.Linq;
using Artemis.UI.Ninject.Factories;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTrackViewModel : PropertyChangedBase
public class PropertyTrackViewModel : Screen
{
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel)
private readonly IPropertyTrackKeyframeViewModelFactory _propertyTrackKeyframeViewModelFactory;
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel,
LayerPropertyViewModel layerPropertyViewModel,
IPropertyTrackKeyframeViewModelFactory propertyTrackKeyframeViewModelFactory)
{
_propertyTrackKeyframeViewModelFactory = propertyTrackKeyframeViewModelFactory;
PropertyTimelineViewModel = propertyTimelineViewModel;
LayerPropertyViewModel = layerPropertyViewModel;
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
@@ -33,7 +39,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
if (KeyframeViewModels.Any(k => k.Keyframe == keyframe))
continue;
KeyframeViewModels.Add(new PropertyTrackKeyframeViewModel(keyframe));
KeyframeViewModels.Add(_propertyTrackKeyframeViewModelFactory.Create(keyframe));
}
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
@@ -42,7 +48,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public void UpdateKeyframes(int pixelsPerSecond)
{
foreach (var keyframeViewModel in KeyframeViewModels)
{
keyframeViewModel.ParentView = View;
keyframeViewModel.Update(pixelsPerSecond);
}
}
protected override void OnViewLoaded()
{
foreach (var keyframeViewModel in KeyframeViewModels)
keyframeViewModel.ParentView = View;
base.OnViewLoaded();
}
}
}