From 8cd9c8a5f81a7da228c6c489b3be375b831a4237 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 26 Jul 2020 17:39:40 +0200 Subject: [PATCH] Timeline segments - Move keyframes on segment changes --- .../BaseLayerPropertyKeyframe.cs | 8 +- .../LayerProperties/LayerPropertyKeyFrame.cs | 22 ++++- .../Abstract/DisplayConditionViewModel.cs | 2 +- .../DisplayConditionPredicateViewModel.cs | 8 +- .../Timeline/TimelineKeyframeViewModel.cs | 24 ++++-- .../Timeline/TimelinePropertyViewModel.cs | 19 ++++- .../Timeline/TimelineSegmentViewModel.cs | 84 ++++++++++++------- .../Timeline/TimelineViewModel.cs | 2 +- 8 files changed, 121 insertions(+), 48 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs index d0537d8b1..e90536c18 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs @@ -1,12 +1,13 @@ using System; using Artemis.Core.Utilities; +using Stylet; namespace Artemis.Core.Models.Profile.LayerProperties { /// /// For internal use only, use instead. /// - public abstract class BaseLayerPropertyKeyframe + public abstract class BaseLayerPropertyKeyframe : PropertyChangedBase { internal BaseLayerPropertyKeyframe(BaseLayerProperty baseLayerProperty) { @@ -27,5 +28,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// The easing function applied on the value of the keyframe /// public Easings.Functions EasingFunction { get; set; } + + /// + /// Removes the keyframe from the layer property + /// + public abstract void Remove(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index af37209aa..a2257cefe 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -6,6 +6,8 @@ namespace Artemis.Core.Models.Profile.LayerProperties public class LayerPropertyKeyframe : BaseLayerPropertyKeyframe { private TimeSpan _position; + private T _value; + private LayerProperty _layerProperty; public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty layerProperty) : base(layerProperty) { @@ -18,12 +20,20 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// The layer property this keyframe is applied to /// - public LayerProperty LayerProperty { get; internal set; } + public LayerProperty LayerProperty + { + get => _layerProperty; + internal set => SetAndNotify(ref _layerProperty, value); + } /// /// The value of this keyframe /// - public T Value { get; set; } + public T Value + { + get => _value; + set => SetAndNotify(ref _value, value); + } /// public override TimeSpan Position @@ -31,9 +41,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties get => _position; set { - _position = value; + SetAndNotify(ref _position, value); LayerProperty.SortKeyframes(); } } + + /// + public override void Remove() + { + LayerProperty.RemoveKeyframe(this); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs index 2c866f348..3c7c92ab2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs @@ -19,7 +19,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.Abstract public abstract void Update(); - public void Delete() + public virtual void Delete() { Model.Parent.RemoveChild(Model); Parent.Update(); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index a2f8034b2..c6770f62d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -23,6 +23,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private readonly IDataModelVisualizationService _dataModelVisualizationService; private readonly IEventAggregator _eventAggregator; private readonly IProfileEditorService _profileEditorService; + private bool _isInitialized; private DataModelPropertiesViewModel _leftSideDataModel; private List _operators; private DataModelPropertiesViewModel _rightSideDataModel; @@ -34,7 +35,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private DataModelVisualizationViewModel _selectedRightSideProperty; private List _supportedInputTypes; - private bool _isInitialized; public DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelVisualizationService dataModelVisualizationService, IDataModelService dataModelService, IEventAggregator eventAggregator) @@ -145,6 +145,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions RightSideInputViewModel.Submit(); } + public override void Delete() + { + base.Delete(); + _profileEditorService.UpdateSelectedProfileElement(); + } + public void Initialize() { // Get the data models diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index 226c88150..e18cc0520 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Utilities; @@ -43,7 +44,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline #endregion } - public abstract class TimelineKeyframeViewModel : PropertyChangedBase + public abstract class TimelineKeyframeViewModel : PropertyChangedBase, IDisposable { private readonly IProfileEditorService _profileEditorService; private BindableCollection _easingViewModels; @@ -57,6 +58,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline _profileEditorService = profileEditorService; BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe; EasingViewModels = new BindableCollection(); + + BaseLayerPropertyKeyframe.PropertyChanged += BaseLayerPropertyKeyframeOnPropertyChanged; } public BaseLayerPropertyKeyframe BaseLayerPropertyKeyframe { get; } @@ -97,6 +100,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public abstract void Delete(); + private void BaseLayerPropertyKeyframeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(BaseLayerPropertyKeyframe.Position)) + Update(_pixelsPerSecond); + } + #region Easing public void CreateEasingViewModels() @@ -121,12 +130,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline private TimeSpan? _offset; - public void ApplyMovement(TimeSpan cursorTime) - { - UpdatePosition(cursorTime); - Update(_pixelsPerSecond); - } - public void ReleaseMovement() { _offset = null; @@ -154,7 +157,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline UpdatePosition(keyframeViewModel.BaseLayerPropertyKeyframe.Position + _offset.Value); } - private void UpdatePosition(TimeSpan position) + public void UpdatePosition(TimeSpan position) { if (position < TimeSpan.Zero) BaseLayerPropertyKeyframe.Position = TimeSpan.Zero; @@ -167,5 +170,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline } #endregion + + public void Dispose() + { + BaseLayerPropertyKeyframe.PropertyChanged -= BaseLayerPropertyKeyframeOnPropertyChanged; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index 2046a9e35..a5e423c2e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -30,14 +30,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList(); var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList(); + foreach (var timelineKeyframeViewModel in toRemove) + timelineKeyframeViewModel.Dispose(); + TimelineKeyframeViewModels.RemoveRange(toRemove); - TimelineKeyframeViewModels.AddRange( - keyframes.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k)) - .Select(k => new TimelineKeyframeViewModel(_profileEditorService, k)) + TimelineKeyframeViewModels.AddRange(keyframes + .Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k)) + .Select(k => new TimelineKeyframeViewModel(_profileEditorService, k)) ); } else - TimelineKeyframeViewModels.Clear(); + DisposeKeyframeViewModels(); foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); @@ -49,6 +52,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline LayerPropertyViewModel.LayerProperty.KeyframeAdded -= LayerPropertyOnKeyframeModified; LayerPropertyViewModel.LayerProperty.KeyframeRemoved -= LayerPropertyOnKeyframeModified; LayerPropertyViewModel.LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframeModified; + DisposeKeyframeViewModels(); + } + + private void DisposeKeyframeViewModels() + { + foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) + timelineKeyframeViewModel.Dispose(); + TimelineKeyframeViewModels.Clear(); } private void LayerPropertyOnKeyframeModified(object sender, EventArgs e) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs index 9175f2b64..cf9aad4ad 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -130,44 +131,66 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public void DisableSegment() { - switch (Segment) + var keyframes = SelectedProfileElement.GetAllKeyframes(); + var startSegmentEnd = SelectedProfileElement.StartSegmentLength; + var mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength; + + var oldSegmentLength = SegmentLength; + + if (Segment == SegmentViewModelType.Start) { - case SegmentViewModelType.Start: - SelectedProfileElement.StartSegmentLength = TimeSpan.Zero; - break; - case SegmentViewModelType.Main: - SelectedProfileElement.MainSegmentLength = TimeSpan.Zero; - break; - case SegmentViewModelType.End: - SelectedProfileElement.EndSegmentLength = TimeSpan.Zero; - break; - default: - throw new ArgumentOutOfRangeException(); + // Remove keyframes that fall in this segment + foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position < startSegmentEnd)) + baseLayerPropertyKeyframe.Remove(); + SelectedProfileElement.StartSegmentLength = TimeSpan.Zero; + } + else if (Segment == SegmentViewModelType.Main) + { + // Remove keyframes that fall in this segment + foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > startSegmentEnd && k.Position < mainSegmentEnd)) + baseLayerPropertyKeyframe.Remove(); + SelectedProfileElement.MainSegmentLength = TimeSpan.Zero; + } + else if (Segment == SegmentViewModelType.End) + { + // Remove keyframes that fall in this segment + foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > mainSegmentEnd)) + baseLayerPropertyKeyframe.Remove(); + SelectedProfileElement.EndSegmentLength = TimeSpan.Zero; } NotifyOfPropertyChange(nameof(SegmentEnabled)); + ShiftNextSegment(SegmentLength - oldSegmentLength); ProfileEditorService.UpdateSelectedProfileElement(); } public void EnableSegment() { - switch (Segment) - { - case SegmentViewModelType.Start: - SelectedProfileElement.StartSegmentLength = TimeSpan.FromSeconds(1); - break; - case SegmentViewModelType.Main: - SelectedProfileElement.MainSegmentLength = TimeSpan.FromSeconds(1); - break; - case SegmentViewModelType.End: - SelectedProfileElement.EndSegmentLength = TimeSpan.FromSeconds(1); - break; - default: - throw new ArgumentOutOfRangeException(); - } + ShiftNextSegment(TimeSpan.FromSeconds(1)); + if (Segment == SegmentViewModelType.Start) + SelectedProfileElement.StartSegmentLength = TimeSpan.FromSeconds(1); + else if (Segment == SegmentViewModelType.Main) + SelectedProfileElement.MainSegmentLength = TimeSpan.FromSeconds(1); + else if (Segment == SegmentViewModelType.End) + SelectedProfileElement.EndSegmentLength = TimeSpan.FromSeconds(1); NotifyOfPropertyChange(nameof(SegmentEnabled)); ProfileEditorService.UpdateSelectedProfileElement(); + UpdateDisplay(); + } + + public void ShiftNextSegment(TimeSpan amount) + { + var segmentEnd = TimeSpan.Zero; + if (Segment == SegmentViewModelType.Start) + segmentEnd = SelectedProfileElement.StartSegmentLength; + else if (Segment == SegmentViewModelType.Main) + segmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength; + else if (Segment == SegmentViewModelType.End) + segmentEnd = SelectedProfileElement.TimelineLength; + + foreach (var baseLayerPropertyKeyframe in SelectedProfileElement.GetAllKeyframes().Where(k => k.Position >= segmentEnd)) + baseLayerPropertyKeyframe.Position += amount; } public void SegmentMouseDown(object sender, MouseButtonEventArgs e) @@ -180,6 +203,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { ((IInputElement) sender).ReleaseMouseCapture(); _draggingSegment = false; + + ProfileEditorService.UpdateSelectedProfileElement(); } public void SegmentMouseMove(object sender, MouseEventArgs e) @@ -189,7 +214,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline // Get the parent scroll viewer, need that for our position var parent = VisualTreeUtilities.FindParent((DependencyObject) sender, "TimelineHeaderScrollViewer"); - + var x = Math.Max(0, e.GetPosition(parent).X); var newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond); @@ -208,7 +233,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0); - + var oldSegmentLength = SegmentLength; if (Segment == SegmentViewModelType.Start) { if (newTime < TimeSpan.FromMilliseconds(100)) @@ -231,12 +256,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline NotifyOfPropertyChange(nameof(SegmentLength)); NotifyOfPropertyChange(nameof(SegmentWidth)); + ShiftNextSegment(SegmentLength - oldSegmentLength); UpdateDisplay(); } private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) || + if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) || e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) || e.PropertyName == nameof(RenderProfileElement.EndSegmentLength)) { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index e76eba964..dc7d0d17c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -220,7 +220,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) keyframeViewModel.SaveOffsetToKeyframe(sourceKeyframeViewModel); - sourceKeyframeViewModel.ApplyMovement(cursorTime); + sourceKeyframeViewModel.UpdatePosition(cursorTime); foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) keyframeViewModel.ApplyOffsetToKeyframe(sourceKeyframeViewModel);