mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
UI - Further restructuring
This commit is contained in:
1 parent
a9c9d06354
commit
678fad95d1
53 files changed
+459
-1242
No files matched your search
File renamed without changes.
-55
@@ -1,55 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.PropertyTimelineView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PropertyTimelineViewModel}">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
|
||||
<BeginStoryboard>
|
||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Grid.Triggers>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding PropertyTrackViewModels}"
|
||||
Width="{Binding Width}"
|
||||
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
|
||||
HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
x:Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.UI.Events;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Artemis.UI.Utilities;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class PropertyTimelineViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
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 += OnSelectedProfileElementUpdated;
|
||||
LayerPropertiesViewModel.PixelsPerSecondChanged += OnPixelsPerSecondChanged;
|
||||
|
||||
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 Dispose()
|
||||
{
|
||||
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
|
||||
LayerPropertiesViewModel.PixelsPerSecondChanged -= OnPixelsPerSecondChanged;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var newViewModels = new List<PropertyTrackViewModel>();
|
||||
foreach (var property in properties)
|
||||
newViewModels.AddRange(CreateViewModels(property));
|
||||
|
||||
PropertyTrackViewModels.Clear();
|
||||
PropertyTrackViewModels.AddRange(newViewModels);
|
||||
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();
|
||||
}
|
||||
|
||||
private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
private List<PropertyTrackViewModel> CreateViewModels(LayerPropertyViewModel property)
|
||||
{
|
||||
var result = new List<PropertyTrackViewModel> {_propertyTrackVmFactory.Create(this, property)};
|
||||
foreach (var child in property.Children)
|
||||
result.AddRange(CreateViewModels(child));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Keyframe movement
|
||||
|
||||
public void MoveSelectedKeyframes(TimeSpan cursorTime)
|
||||
{
|
||||
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
|
||||
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
|
||||
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||
keyframeViewModel.ApplyMovement(cursorTime);
|
||||
|
||||
_profileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
|
||||
|
||||
public void ReleaseSelectedKeyframes()
|
||||
{
|
||||
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
|
||||
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||
keyframeViewModel.ReleaseMovement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keyframe selection
|
||||
|
||||
private Point _mouseDragStartPoint;
|
||||
private bool _mouseDragging;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released)
|
||||
return;
|
||||
|
||||
((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;
|
||||
|
||||
var selectedKeyframes = HitTestUtilities.GetHitViewModels<PropertyTrackKeyframeViewModel>((Visual) sender, SelectionRectangle);
|
||||
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
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
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<T> : PropertyTrackKeyframeViewModel
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private int _pixelsPerSecond;
|
||||
|
||||
public PropertyTrackKeyframeViewModel(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe<T> keyframe, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
|
||||
PropertyTrackViewModel = propertyTrackViewModel;
|
||||
Keyframe = keyframe;
|
||||
EasingViewModels = new BindableCollection<PropertyTrackEasingViewModel>();
|
||||
CreateEasingViewModels();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
public PropertyTrackViewModel PropertyTrackViewModel { get; }
|
||||
public LayerPropertyKeyframe<T> 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)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released)
|
||||
return;
|
||||
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected)
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, true, false);
|
||||
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, true);
|
||||
else if (!IsSelected)
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, false);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.ReleaseSelectedKeyframes();
|
||||
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
public void KeyframeMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView)));
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private TimeSpan GetCursorTime(Point position)
|
||||
{
|
||||
// Get the parent grid, need that for our position
|
||||
var x = Math.Max(0, position.X);
|
||||
var time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
|
||||
|
||||
// Round the time to something that fits the current zoom level
|
||||
if (_pixelsPerSecond < 200)
|
||||
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0);
|
||||
else if (_pixelsPerSecond < 500)
|
||||
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0);
|
||||
else
|
||||
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds));
|
||||
|
||||
// If shift is held, snap to the current time
|
||||
// Take a tolerance of 5 pixels (half a keyframe width)
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
{
|
||||
var tolerance = 1000f / _pixelsPerSecond * 5;
|
||||
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
|
||||
time = _profileEditorService.CurrentTime;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Context menu actions
|
||||
|
||||
public void Copy()
|
||||
{
|
||||
var keyframe = PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.CreateNewKeyframe(Keyframe.Position, Keyframe.BaseValue);
|
||||
keyframe.EasingFunction = Keyframe.EasingFunction;
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.RemoveKeyframe(Keyframe);
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Easing
|
||||
|
||||
private void CreateEasingViewModels()
|
||||
{
|
||||
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new PropertyTrackEasingViewModel(this, v)));
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#region Movement
|
||||
|
||||
private bool _movementReleased = true;
|
||||
private TimeSpan _startOffset;
|
||||
|
||||
public void ApplyMovement(TimeSpan cursorTime)
|
||||
{
|
||||
if (_movementReleased)
|
||||
{
|
||||
_movementReleased = false;
|
||||
_startOffset = cursorTime - Keyframe.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Keyframe.Position = cursorTime - _startOffset;
|
||||
if (Keyframe.Position < TimeSpan.Zero)
|
||||
Keyframe.Position = TimeSpan.Zero;
|
||||
|
||||
Update(_pixelsPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseMovement()
|
||||
{
|
||||
_movementReleased = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class PropertyTrackKeyframeViewModel : PropertyChangedBase
|
||||
{
|
||||
}
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.PropertyTrackView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="20" d:DesignWidth="600"
|
||||
d:DataContext="{d:DesignInstance local:PropertyTrackViewModel}">
|
||||
<Border Height="25"
|
||||
BorderThickness="0,0,0,1"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}"
|
||||
Visibility="{Binding MustDisplay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl ItemsSource="{Binding KeyframeViewModels}"
|
||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||
Stroke="White"
|
||||
StrokeThickness="0"
|
||||
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.Style>
|
||||
<Style TargetType="{x:Type Ellipse}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
<Ellipse.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Copy" Command="{s:Action Copy}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Delete" Command="{s:Action Delete}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Delete" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<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>
|
||||
</Border>
|
||||
</UserControl>
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class PropertyTrackViewModel : Screen
|
||||
{
|
||||
private readonly IPropertyTrackKeyframeVmFactory _propertyTrackKeyframeVmFactory;
|
||||
|
||||
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel,
|
||||
IPropertyTrackKeyframeVmFactory propertyTrackKeyframeVmFactory)
|
||||
{
|
||||
_propertyTrackKeyframeVmFactory = propertyTrackKeyframeVmFactory;
|
||||
PropertyTimelineViewModel = propertyTimelineViewModel;
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
|
||||
|
||||
PopulateKeyframes();
|
||||
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
||||
|
||||
LayerPropertyViewModel.ExpandedStateChanged += (sender, args) => UpdateMustDisplay();
|
||||
LayerPropertyViewModel.LayerProperty.VisibilityChanged += (sender, args) => UpdateMustDisplay();
|
||||
UpdateMustDisplay();
|
||||
}
|
||||
|
||||
|
||||
public PropertyTimelineViewModel PropertyTimelineViewModel { get; }
|
||||
public LayerPropertyViewModel LayerPropertyViewModel { get; }
|
||||
public BindableCollection<PropertyTrackKeyframeViewModel> KeyframeViewModels { get; set; }
|
||||
public bool MustDisplay { get; set; }
|
||||
|
||||
private void UpdateMustDisplay()
|
||||
{
|
||||
var expandedTest = LayerPropertyViewModel.Parent;
|
||||
while (expandedTest != null)
|
||||
{
|
||||
if (!expandedTest.IsExpanded)
|
||||
{
|
||||
MustDisplay = false;
|
||||
return;
|
||||
}
|
||||
expandedTest = expandedTest.Parent;
|
||||
}
|
||||
|
||||
var visibilityTest = LayerPropertyViewModel.LayerProperty;
|
||||
while (visibilityTest != null)
|
||||
{
|
||||
if (visibilityTest.IsHidden)
|
||||
{
|
||||
MustDisplay = false;
|
||||
return;
|
||||
}
|
||||
visibilityTest = visibilityTest.Parent;
|
||||
}
|
||||
|
||||
MustDisplay = true;
|
||||
}
|
||||
|
||||
public void PopulateKeyframes()
|
||||
{
|
||||
// Remove old keyframes
|
||||
KeyframeViewModels.RemoveRange(KeyframeViewModels.ToList().Where(vm => !LayerPropertyViewModel.LayerProperty.UntypedKeyframes.Contains(vm.Keyframe)));
|
||||
|
||||
// Add new keyframes
|
||||
KeyframeViewModels.AddRange(
|
||||
LayerPropertyViewModel.LayerProperty.UntypedKeyframes
|
||||
.Where(k => KeyframeViewModels.All(vm => vm.Keyframe != k))
|
||||
.Select(k => _propertyTrackKeyframeVmFactory.Create(this, k))
|
||||
);
|
||||
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelinePropertyGroupViewModel
|
||||
{
|
||||
public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelinePropertyViewModel<T> : TimelinePropertyViewModel
|
||||
{
|
||||
public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TimelinePropertyViewModel : IDisposable
|
||||
{
|
||||
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelineViewModel
|
||||
{
|
||||
public TimelineViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
||||
{
|
||||
LayerPropertyGroups = layerPropertyGroups;
|
||||
}
|
||||
|
||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user