Files
Artemis/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs
T
SpoinkyNL 05cf1f0b53 Layer properties - Added drag-editing for number-based properties
Layer properties - Added min and max values
Nuget - Updated Stylet to 1.3.1
2020-02-19 00:17:23 +01:00

227 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTimelineViewModel : PropertyChangedBase
{
private readonly IProfileEditorService _profileEditorService;
private readonly IPropertyTrackVmFactory _propertyTrackVmFactory;
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel,
IProfileEditorService profileEditorService,
IPropertyTrackVmFactory propertyTrackVmFactory)
{
_profileEditorService = profileEditorService;
_propertyTrackVmFactory = propertyTrackVmFactory;
LayerPropertiesViewModel = layerPropertiesViewModel;
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
_profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update();
LayerPropertiesViewModel.PixelsPerSecondChanged += (sender, args) => UpdateKeyframePositions();
Execute.PostToUIThread(() => SelectionRectangle = new RectangleGeometry());
}
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
public double Width { get; set; }
public BindableCollection<PropertyTrackViewModel> PropertyTrackViewModels { get; set; }
public RectangleGeometry SelectionRectangle { get; set; }
public void UpdateEndTime()
{
// End time is the last keyframe + 10 sec
var lastKeyFrame = PropertyTrackViewModels.SelectMany(r => r.KeyframeViewModels).OrderByDescending(t => t.Keyframe.Position).FirstOrDefault();
var endTime = lastKeyFrame?.Keyframe.Position.Add(new TimeSpan(0, 0, 0, 10)) ?? TimeSpan.FromSeconds(10);
Width = endTime.TotalSeconds * LayerPropertiesViewModel.PixelsPerSecond;
// Ensure the caret isn't outside the end time
if (_profileEditorService.CurrentTime > endTime)
_profileEditorService.CurrentTime = endTime;
}
public void PopulateProperties(List<LayerPropertyViewModel> properties)
{
PropertyTrackViewModels.Clear();
foreach (var property in properties)
CreateViewModels(property);
UpdateEndTime();
}
public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
// Determine the index by flattening all the layer's properties
var index = layerPropertyViewModel.LayerProperty.GetFlattenedIndex();
if (index > PropertyTrackViewModels.Count)
index = PropertyTrackViewModels.Count;
PropertyTrackViewModels.Insert(index, _propertyTrackVmFactory.Create(this, layerPropertyViewModel));
}
public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
var vm = PropertyTrackViewModels.FirstOrDefault(v => v.LayerPropertyViewModel == layerPropertyViewModel);
if (vm != null)
PropertyTrackViewModels.Remove(vm);
}
public void UpdateKeyframePositions()
{
foreach (var viewModel in PropertyTrackViewModels)
viewModel.UpdateKeyframes(LayerPropertiesViewModel.PixelsPerSecond);
UpdateEndTime();
}
/// <summary>
/// Updates the time line's keyframes
/// </summary>
public void Update()
{
foreach (var viewModel in PropertyTrackViewModels)
viewModel.PopulateKeyframes();
UpdateEndTime();
}
#region Keyframe movement
public void MoveSelectedKeyframes(TimeSpan offset)
{
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
{
// TODO: Not ideal as this stacks them all if they get to 0, oh well
if (keyframeViewModel.Keyframe.Position + offset > TimeSpan.Zero)
{
keyframeViewModel.Keyframe.Position += offset;
keyframeViewModel.Update(LayerPropertiesViewModel.PixelsPerSecond);
}
}
_profileEditorService.UpdateProfilePreview();
}
#endregion
private void CreateViewModels(LayerPropertyViewModel property)
{
PropertyTrackViewModels.Add(_propertyTrackVmFactory.Create(this, property));
foreach (var child in property.Children)
CreateViewModels(child);
}
#region Keyframe selection
private Point _mouseDragStartPoint;
private bool _mouseDragging;
// ReSharper disable once UnusedMember.Global - Called from view
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
{
((IInputElement) sender).CaptureMouse();
SelectionRectangle.Rect = new Rect();
_mouseDragStartPoint = e.GetPosition((IInputElement) sender);
_mouseDragging = true;
e.Handled = true;
}
// ReSharper disable once UnusedMember.Global - Called from view
public void TimelineCanvasMouseUp(object sender, MouseEventArgs e)
{
if (!_mouseDragging)
return;
var position = e.GetPosition((IInputElement) sender);
var selectedRect = new Rect(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect;
// Find all keyframes in the rectangle
var selectedKeyframes = new List<PropertyTrackKeyframeViewModel>();
var hitTestParams = new GeometryHitTestParameters(SelectionRectangle);
var resultCallback = new HitTestResultCallback(result => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(element =>
{
if (element is Ellipse ellipse)
selectedKeyframes.Add((PropertyTrackKeyframeViewModel) ellipse.DataContext);
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest((Visual) sender, filterCallback, resultCallback, hitTestParams);
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel);
_mouseDragging = false;
e.Handled = true;
((IInputElement) sender).ReleaseMouseCapture();
}
public void TimelineCanvasMouseMove(object sender, MouseEventArgs e)
{
if (_mouseDragging && e.LeftButton == MouseButtonState.Pressed)
{
var position = e.GetPosition((IInputElement) sender);
var selectedRect = new Rect(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect;
e.Handled = true;
}
}
public void SelectKeyframe(PropertyTrackKeyframeViewModel clicked, bool selectBetween, bool toggle)
{
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
if (selectBetween)
{
var selectedIndex = keyframeViewModels.FindIndex(k => k.IsSelected);
// If nothing is selected, select only the clicked
if (selectedIndex == -1)
{
clicked.IsSelected = true;
return;
}
foreach (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = false;
var clickedIndex = keyframeViewModels.IndexOf(clicked);
if (clickedIndex < selectedIndex)
{
foreach (var keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1))
keyframeViewModel.IsSelected = true;
}
else
{
foreach (var keyframeViewModel in keyframeViewModels.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1))
keyframeViewModel.IsSelected = true;
}
}
else if (toggle)
{
// Toggle only the clicked keyframe, leave others alone
clicked.IsSelected = !clicked.IsSelected;
}
else
{
// Only select the clicked keyframe
foreach (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = false;
clicked.IsSelected = true;
}
}
#endregion
}
}