From 0e74e5b34ee19eff2cc431ef780646cf7893b6e0 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Fri, 27 Sep 2019 23:51:30 +0800 Subject: [PATCH] add GeometryAnimationUsingKeyFrames --- .../Resources/Themes/Basic/Geometries.xaml | 2 + .../HandyControl_Shared.projitems | 1 + .../Animation/DiscreteGeometryKeyFrame.cs | 31 +- .../Media/Animation/EasingGeometryKeyFrame.cs | 13 +- .../Media/Animation/GeometryAnimationBase.cs | 5 +- .../GeometryAnimationUsingKeyFrames.cs | 506 +++++++++++++++++- .../Media/Animation/GeometryKeyFrame.cs | 32 +- .../Animation/GeometryKeyFrameCollection.cs | 2 +- .../Media/Animation/LinearGeometryKeyFrame.cs | 13 +- .../Media/Animation/ResolvedKeyFrameEntry.cs | 38 ++ .../Media/Animation/SplineGeometryKeyFrame.cs | 13 +- .../Tools/Helper/AnimationHelper.cs | 12 + 12 files changed, 595 insertions(+), 73 deletions(-) create mode 100644 src/Shared/HandyControl_Shared/Media/Animation/ResolvedKeyFrameEntry.cs diff --git a/src/Shared/HandyControlDemo_Shared/Resources/Themes/Basic/Geometries.xaml b/src/Shared/HandyControlDemo_Shared/Resources/Themes/Basic/Geometries.xaml index 0860ffdd..ca18bb48 100644 --- a/src/Shared/HandyControlDemo_Shared/Resources/Themes/Basic/Geometries.xaml +++ b/src/Shared/HandyControlDemo_Shared/Resources/Themes/Basic/Geometries.xaml @@ -9,6 +9,8 @@ M449.722947 199.412211H706.021053c130.967579 0 237.136842 106.169263 237.136842 237.136842v256.298105c0 130.967579-106.169263 237.136842-237.136842 237.136842H449.722947c-130.965895 0-237.136842-106.169263-237.136842-237.136842V436.549053c0-130.967579 106.170947-237.136842 237.136842-237.136842z m-201.205894-21.557895c0-64.299789-70.085053-104.704-125.850948-72.55579s-55.764211 112.96 0 145.111579 125.850947-8.257684 125.850948-72.555789z m239.528421 234.74021c0-68.003368-74.088421-110.736842-133.037474-76.734315s-58.950737 119.467789 0 153.470315 133.039158-8.730947 133.039158-76.736zM835.368421 692.847158c0-117.596632-128.154947-191.494737-230.123789-132.695579s-101.970526 206.593684 0 265.391158S835.368421 810.443789 835.368421 692.847158z M430.079734 481.464543c45.196365 45.596478 118.486578 45.596478 163.68499 0l360.684736-324.939627c0.547469 3.751441 1.13894 7.490601 1.13894 11.401678l0 404.753417 0 116.754121c0 42.995233-34.547841 77.838809-77.145008 77.838809L145.397996 767.272941c-42.597167 0-77.145008-34.844599-77.145008-77.838809L68.252988 494.839155 68.252988 167.926594c0-3.911076 0.593518-7.65126 1.139963-11.401678L430.079734 481.464543zM910.960992 74.226538 562.044278 387.28558c-38.470173 38.811957-62.276369 38.811957-100.770078 0L112.538611 74.408687c10.011005-4.789073 21.069875-7.673773 32.859385-7.673773l733.044372 0C890.095779 66.735937 901.065622 69.539796 910.960992 74.226538z M260.8 645H160V0h100.8v645zM461.8 152.2h-100.8V1024h100.8V152.2z m201.2 0h-100.8V1024h100.8V152.2zM864 152h-100.8v494H864V152z + m 41.416254,90 c -0.327378,-7.4702 0.20833,-32.7284 0,-39.901 -5.386902,-0.2083 -4.521603,0.3274 -9.848987,0 0.20833,-5.50595 0.36436,-7.66666 0.126269,-13.32142 4.646472,0.0181 3.439989,-0.009 9.848987,-0.1894 0.09586,-3.7736 0.133082,-3.0791 0.126269,-7.38674 0.18259,-3.73943 -0.486609,-10.54308 4.293149,-14.96288 4.779758,-4.4198 13.606811,-3.64808 22.223356,-3.53554 -0.04417,5.73754 -0.03936,9.37986 0,12.87945 -5.049924,0.46388 -7.309188,-0.33689 -10.85914,1.26269 -1.403378,3.17794 -1.569601,4.80531 -1.262691,11.93242 3.147964,-0.13336 8.201788,-0.1378 12.626907,0 -0.995158,6.00899 -0.948285,7.62376 -1.767767,13.06882 -3.676625,0.088 -5.605721,-0.1488 -11.111678,0 -0.148814,6.756 0.357147,33.0107 0,40.1536 -6.428576,0.1786 -8.174438,-0.03 -14.394674,0 z + m 10.44335,90 c 11.073313,0.3952 19.483106,-1.8358 23.901837,-7.1603 -7.9736,-1.4292 -11.832311,-4.1933 -15.078321,-11.0837 3.459698,0.8219 5.795894,0.6358 7.606781,-0.607 -7.19593,-1.719 -12.734543,-6.7971 -13.741664,-15.836 2.766355,1.55307 5.466848,2.66623 7.828682,2.0203 -4.336544,-2.92911 -9.838998,-10.47636 -5.555839,-22.47589 8.400675,11.87052 23.824269,17.67568 33.840111,17.67767 -0.936406,-9.74688 5.88057,-19.46521 15.302849,-19.97853 8.13118,-0.50719 10.57457,4.01944 12.476346,4.82624 3.644547,0.13419 7.393301,-1.74401 10.354063,-3.53553 -1.380842,4.47157 -5.06769,5.62903 -6.313453,8.58629 5.42317,0.41513 5.891376,-1.53111 8.333758,-2.0203 -2.071414,3.75017 -5.393863,5.00034 -7.323606,8.08122 -1.633654,16.12573 -5.16049,27.57123 -14.647212,36.36553 -13.825764,11.3764 -34.755458,17.369 -56.984332,5.14 z M68.191078 915.462005l161.384597 0L229.575676 431.30719 68.191078 431.30719 68.191078 915.462005zM955.808922 471.653083c0-44.582381-36.109406-80.69281-80.69281-80.69281L620.329241 390.960273 658.859789 206.578915c0.807389-4.034896 1.412163-8.271384 1.412163-12.709463 0-16.743336-6.859221-31.873941-17.752316-42.767036l-42.968627-42.565445L333.871043 374.216937c-14.524808 14.7264-23.602557 34.899858-23.602557 57.090253l0 403.462005c0 44.582381 36.109406 80.69281 80.69281 80.69281l363.116111 0c33.487695 0 62.133106-20.37505 74.236771-49.222051l121.643478-284.441261c3.63069-9.279341 5.850242-19.164478 5.850242-29.452799L955.807898 475.083206l-0.403183-0.403183L955.808922 471.653083z M736 224H288a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64V288a64 64 0 0 0-64-64z m-416 160a64 64 0 1 1 128.032 0.032A64 64 0 0 1 320 384z m416 320a32 32 0 0 1-32 32H384a32 32 0 0 1-32-32l96-160c26.432-2.048 68.128 6.016 128 64l128-224a32 32 0 0 1 32 32v288z M19.692308 512A25 25 0 1 1 19.692308 531.692308ZM512 303.027832C512 286.548884 524.86564 273.174666 540.717948 273.174666L598.153846 273.174666C614.006154 273.174666 626.871794 286.548884 626.871794 303.027832L626.871794 362.734167C626.871794 379.213115 614.006154 392.587333 598.153846 392.587333L540.717948 392.587333C524.86564 392.587333 512 379.213115 512 362.734167L512 303.027832 512 303.027832ZM512 482.146834 512 541.853166C512 558.332115 499.13436 571.706335 483.282052 571.706335L425.846154 571.706335C409.993846 571.706335 397.128206 558.332115 397.128206 541.853166L397.128206 482.146834C397.128206 465.667885 409.993846 452.293665 425.846154 452.293665L483.282052 452.293665C499.13436 452.293665 512 465.667885 512 482.146834L512 482.146834ZM339.692308 303.027832C339.692308 286.548884 352.557948 273.174666 368.410256 273.174666L425.846154 273.174666C441.698462 273.174666 454.564102 286.548884 454.564102 303.027832L454.564102 362.734167C454.564102 379.213115 441.698462 392.587333 425.846154 392.587333L368.410256 392.587333C352.557948 392.587333 339.692308 379.213115 339.692308 362.734167L339.692308 303.027832 339.692308 303.027832ZM167.384615 303.027832C167.384615 286.548884 180.250256 273.174666 196.102564 273.174666L253.538462 273.174666C269.390769 273.174666 282.25641 286.548884 282.25641 303.027832L282.25641 362.734167C282.25641 379.213115 269.390769 392.587333 253.538462 392.587333L196.102564 392.587333C180.250256 392.587333 167.384615 379.213115 167.384615 362.734167L167.384615 303.027832 167.384615 303.027832ZM282.25641 720.972168C282.25641 737.451116 269.390769 750.825334 253.538462 750.825334L196.102564 750.825334C180.250256 750.825334 167.384615 737.451116 167.384615 720.972168L167.384615 661.265833C167.384615 644.786885 180.250256 631.412667 196.102564 631.412667L253.538462 631.412667C269.390769 631.412667 282.25641 644.786885 282.25641 661.265833L282.25641 720.972168 282.25641 720.972168ZM310.97436 571.706335 253.538462 571.706335C237.686154 571.706335 224.820513 558.332115 224.820513 541.853166L224.820513 482.146834C224.820513 465.667885 237.686154 452.293665 253.538462 452.293665L310.97436 452.293665C326.826667 452.293665 339.692308 465.667885 339.692308 482.146834L339.692308 541.853166C339.692308 558.332115 326.826667 571.706335 310.97436 571.706335L310.97436 571.706335ZM569.435898 720.972168C569.435898 737.451116 556.570256 750.825334 540.717948 750.825334L368.410256 750.825334C352.557948 750.825334 339.692308 737.451116 339.692308 720.972168L339.692308 661.265833C339.692308 644.786885 352.557948 631.412667 368.410256 631.412667L540.717948 631.412667C556.570256 631.412667 569.435898 644.786885 569.435898 661.265833L569.435898 720.972168 569.435898 720.972168ZM569.435898 541.853166 569.435898 482.146834C569.435898 465.667885 582.301538 452.293665 598.153846 452.293665L655.589744 452.293665C671.442052 452.293665 684.307692 465.667885 684.307692 482.146834L684.307692 541.853166C684.307692 558.332115 671.442052 571.706335 655.589744 571.706335L598.153846 571.706335C582.301538 571.706335 569.435898 558.332115 569.435898 541.853166L569.435898 541.853166ZM856.615385 720.972168C856.615385 737.451116 843.749744 750.825334 827.897437 750.825334L655.589744 750.825334C639.737437 750.825334 626.871794 737.451116 626.871794 720.972168L626.871794 661.265833C626.871794 644.786885 639.737437 631.412667 655.589744 631.412667L713.02564 631.412667C728.877948 631.412667 741.74359 618.038449 741.74359 601.559501L741.74359 482.146834C741.74359 465.667885 754.609231 452.293665 770.461538 452.293665L827.897437 452.293665C843.749744 452.293665 856.615385 465.667885 856.615385 482.146834L856.615385 720.972168 856.615385 720.972168ZM856.615385 362.734167C856.615385 379.213115 843.749744 392.587333 827.897437 392.587333L713.02564 392.587333C697.173333 392.587333 684.307692 379.213115 684.307692 362.734167L684.307692 303.027832C684.307692 286.548884 697.173333 273.174666 713.02564 273.174666L827.897437 273.174666C843.749744 273.174666 856.615385 286.548884 856.615385 303.027832L856.615385 362.734167 856.615385 362.734167Z diff --git a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems index 82d0c876..96093d43 100644 --- a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems +++ b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems @@ -49,6 +49,7 @@ + diff --git a/src/Shared/HandyControl_Shared/Media/Animation/DiscreteGeometryKeyFrame.cs b/src/Shared/HandyControl_Shared/Media/Animation/DiscreteGeometryKeyFrame.cs index 047e4c5f..2a228e9f 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/DiscreteGeometryKeyFrame.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/DiscreteGeometryKeyFrame.cs @@ -2,21 +2,24 @@ using System.Windows.Media; using System.Windows.Media.Animation; -public class DiscreteGeometryKeyFrame : GeometryKeyFrame +namespace HandyControl.Media.Animation { - public DiscreteGeometryKeyFrame() + public class DiscreteGeometryKeyFrame : GeometryKeyFrame { + public DiscreteGeometryKeyFrame() + { + } + + public DiscreteGeometryKeyFrame(Geometry value) : base(value) + { + } + + public DiscreteGeometryKeyFrame(Geometry value, KeyTime keyTime) : base(value, keyTime) + { + } + + protected override Freezable CreateInstanceCore() => new DiscreteGeometryKeyFrame(); + + protected override double[] InterpolateValueCore(double[] baseValue, double keyFrameProgress) => keyFrameProgress < 1.0 ? baseValue : Numbers; } - - public DiscreteGeometryKeyFrame(Geometry value) : base(value) - { - } - - public DiscreteGeometryKeyFrame(Geometry value, KeyTime keyTime) : base(value, keyTime) - { - } - - protected override Freezable CreateInstanceCore() => new DiscreteGeometryKeyFrame(); - - protected override Geometry InterpolateValueCore(Geometry baseValue, double keyFrameProgress) => keyFrameProgress < 1.0 ? baseValue : Value; } \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Media/Animation/EasingGeometryKeyFrame.cs b/src/Shared/HandyControl_Shared/Media/Animation/EasingGeometryKeyFrame.cs index 2bba2835..7c70bef3 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/EasingGeometryKeyFrame.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/EasingGeometryKeyFrame.cs @@ -8,8 +8,6 @@ namespace HandyControl.Media.Animation { public class EasingGeometryKeyFrame : GeometryKeyFrame { - private double[] _baseValues; - public EasingGeometryKeyFrame() { @@ -32,13 +30,8 @@ namespace HandyControl.Media.Animation protected override Freezable CreateInstanceCore() => new EasingGeometryKeyFrame(); - protected override Geometry InterpolateValueCore(Geometry baseValue, double keyFrameProgress) + protected override double[] InterpolateValueCore(double[] baseValue, double keyFrameProgress) { - if (_baseValues == null) - { - AnimationHelper.DecomposeGeometryStr(baseValue.ToString(), out _baseValues); - } - var easingFunction = EasingFunction; if (easingFunction != null) { @@ -52,10 +45,10 @@ namespace HandyControl.Media.Animation if (MathHelper.AreClose(keyFrameProgress, 1)) { - return AnimationHelper.ComposeGeometry(Strings, Value); + return Numbers; } - return AnimationHelper.InterpolateGeometry(_baseValues, Value, keyFrameProgress, Strings); + return AnimationHelper.InterpolateGeometryValue(baseValue, Numbers, keyFrameProgress); } public static readonly DependencyProperty EasingFunctionProperty = DependencyProperty.Register( diff --git a/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationBase.cs b/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationBase.cs index 0032e9b2..eee8c262 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationBase.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationBase.cs @@ -6,10 +6,7 @@ namespace HandyControl.Media.Animation { public abstract class GeometryAnimationBase : AnimationTimeline { - public new GeometryAnimationBase Clone() - { - return (GeometryAnimationBase)base.Clone(); - } + public new GeometryAnimationBase Clone() => (GeometryAnimationBase)base.Clone(); public sealed override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock) { diff --git a/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationUsingKeyFrames.cs b/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationUsingKeyFrames.cs index 1405806e..38b89150 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationUsingKeyFrames.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/GeometryAnimationUsingKeyFrames.cs @@ -1,34 +1,518 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text.RegularExpressions; using System.Windows; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Animation; +using HandyControl.Tools; + +// ReSharper disable PossibleInvalidOperationException namespace HandyControl.Media.Animation { [ContentProperty("KeyFrames")] public class GeometryAnimationUsingKeyFrames : GeometryAnimationBase, IKeyFrameAnimation, IAddChild { - protected override Freezable CreateInstanceCore() + private string[] _strings; + + public string[] Strings { - throw new System.NotImplementedException(); + get + { + if (_keyFrames == null || _keyFrames.Count == 0) + { + return null; + } + + return _strings ??= Regex.Split(_keyFrames[0].Value.ToString(), RegularPatterns.DigitsPattern); + } } - protected override Geometry GetCurrentValueCore(Geometry defaultOriginValue, Geometry defaultDestinationValue, - AnimationClock animationClock) + private GeometryKeyFrameCollection _keyFrames; + + private ResolvedKeyFrameEntry[] _sortedResolvedKeyFrames; + + private bool _areKeyTimesValid; + + public GeometryAnimationUsingKeyFrames() => _areKeyTimesValid = true; + + public new GeometryAnimationUsingKeyFrames Clone() => (GeometryAnimationUsingKeyFrames)base.Clone(); + + public new GeometryAnimationUsingKeyFrames CloneCurrentValue() => (GeometryAnimationUsingKeyFrames)base.CloneCurrentValue(); + + protected override bool FreezeCore(bool isChecking) { - throw new System.NotImplementedException(); + var canFreeze = base.FreezeCore(isChecking); + + canFreeze &= Freeze(_keyFrames, isChecking); + + if (canFreeze & !_areKeyTimesValid) + { + ResolveKeyTimes(); + } + + return canFreeze; } - public IList KeyFrames { get; set; } - public void AddChild(object value) + protected override void OnChanged() { - throw new System.NotImplementedException(); + _areKeyTimesValid = false; + + base.OnChanged(); } - public void AddText(string text) + protected override Freezable CreateInstanceCore() => new GeometryAnimationUsingKeyFrames(); + + protected override void CloneCore(Freezable sourceFreezable) { - throw new System.NotImplementedException(); + var sourceAnimation = (GeometryAnimationUsingKeyFrames)sourceFreezable; + base.CloneCore(sourceFreezable); + + CopyCommon(sourceAnimation, false); + } + + protected override void CloneCurrentValueCore(Freezable sourceFreezable) + { + var sourceAnimation = (GeometryAnimationUsingKeyFrames)sourceFreezable; + base.CloneCurrentValueCore(sourceFreezable); + + CopyCommon(sourceAnimation, true); + } + + protected override void GetAsFrozenCore(Freezable source) + { + var sourceAnimation = (GeometryAnimationUsingKeyFrames)source; + base.GetAsFrozenCore(source); + + CopyCommon(sourceAnimation, false); + } + + protected override void GetCurrentValueAsFrozenCore(Freezable source) + { + var sourceAnimation = (GeometryAnimationUsingKeyFrames)source; + base.GetCurrentValueAsFrozenCore(source); + + CopyCommon(sourceAnimation, true); + } + + private void CopyCommon(GeometryAnimationUsingKeyFrames sourceAnimation, bool isCurrentValueClone) + { + _areKeyTimesValid = sourceAnimation._areKeyTimesValid; + + if (_areKeyTimesValid && sourceAnimation._sortedResolvedKeyFrames != null) + { + _sortedResolvedKeyFrames = (ResolvedKeyFrameEntry[])sourceAnimation._sortedResolvedKeyFrames.Clone(); + } + + if (sourceAnimation._keyFrames != null) + { + if (isCurrentValueClone) + { + _keyFrames = (GeometryKeyFrameCollection)sourceAnimation._keyFrames.CloneCurrentValue(); + } + else + { + _keyFrames = sourceAnimation._keyFrames.Clone(); + } + + OnFreezablePropertyChanged(null, _keyFrames); + } + } + + void IAddChild.AddChild(object child) + { + WritePreamble(); + + if (child == null) + { + throw new ArgumentNullException(nameof(child)); + } + + AddChild(child); + + WritePostscript(); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void AddChild(object child) + { + if (child is GeometryKeyFrame keyFrame) + { + KeyFrames.Add(keyFrame); + } + else + { + throw new ArgumentException("Animation_ChildMustBeKeyFrame", nameof(child)); + } + } + + void IAddChild.AddText(string childText) + { + if (childText == null) + { + throw new ArgumentNullException(nameof(childText)); + } + + AddText(childText); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void AddText(string childText) => throw new InvalidOperationException("Animation_NoTextChildren"); + + protected override Geometry GetCurrentValueCore(Geometry defaultOriginValue, Geometry defaultDestinationValue, AnimationClock animationClock) + { + if (_keyFrames == null) + { + return defaultDestinationValue; + } + + if (!_areKeyTimesValid) + { + ResolveKeyTimes(); + } + + if (_sortedResolvedKeyFrames == null) + { + return defaultDestinationValue; + } + + var currentTime = animationClock.CurrentTime.Value; + var keyFrameCount = _sortedResolvedKeyFrames.Length; + var maxKeyFrameIndex = keyFrameCount - 1; + + double[] currentIterationValue; + + var currentResolvedKeyFrameIndex = 0; + + while (currentResolvedKeyFrameIndex < keyFrameCount && currentTime > _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime) + { + currentResolvedKeyFrameIndex++; + } + + while (currentResolvedKeyFrameIndex < maxKeyFrameIndex && currentTime == _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex + 1]._resolvedKeyTime) + { + currentResolvedKeyFrameIndex++; + } + + if (currentResolvedKeyFrameIndex == keyFrameCount) + { + currentIterationValue = GetResolvedKeyFrameValue(maxKeyFrameIndex); + } + else if (currentTime == _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime) + { + currentIterationValue = GetResolvedKeyFrameValue(currentResolvedKeyFrameIndex); + } + else + { + double currentSegmentProgress; + double[] fromValue; + + if (currentResolvedKeyFrameIndex == 0) + { + AnimationHelper.DecomposeGeometryStr(defaultOriginValue.ToString(), out fromValue); + + currentSegmentProgress = currentTime.TotalMilliseconds / _sortedResolvedKeyFrames[0]._resolvedKeyTime.TotalMilliseconds; + } + else + { + var previousResolvedKeyFrameIndex = currentResolvedKeyFrameIndex - 1; + var previousResolvedKeyTime = _sortedResolvedKeyFrames[previousResolvedKeyFrameIndex]._resolvedKeyTime; + + fromValue = GetResolvedKeyFrameValue(previousResolvedKeyFrameIndex); + + var segmentCurrentTime = currentTime - previousResolvedKeyTime; + var segmentDuration = _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime - previousResolvedKeyTime; + + currentSegmentProgress = segmentCurrentTime.TotalMilliseconds / segmentDuration.TotalMilliseconds; + } + + currentIterationValue = GetResolvedKeyFrame(currentResolvedKeyFrameIndex).InterpolateValue(fromValue, currentSegmentProgress); + } + + return AnimationHelper.ComposeGeometry(Strings, currentIterationValue); + } + + protected sealed override Duration GetNaturalDurationCore(Clock clock) => new Duration(LargestTimeSpanKeyTime); + + IList IKeyFrameAnimation.KeyFrames + { + get => KeyFrames; + set => KeyFrames = (GeometryKeyFrameCollection)value; + } + + public GeometryKeyFrameCollection KeyFrames + { + get + { + ReadPreamble(); + + if (_keyFrames == null) + { + if (IsFrozen) + { + _keyFrames = GeometryKeyFrameCollection.Empty; + } + else + { + WritePreamble(); + + _keyFrames = new GeometryKeyFrameCollection(); + + OnFreezablePropertyChanged(null, _keyFrames); + + WritePostscript(); + } + } + + return _keyFrames; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + WritePreamble(); + + if (value != _keyFrames) + { + OnFreezablePropertyChanged(_keyFrames, value); + _keyFrames = value; + + WritePostscript(); + } + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeKeyFrames() + { + ReadPreamble(); + + return _keyFrames != null && _keyFrames.Count > 0; + } + + private struct KeyTimeBlock + { + public int BeginIndex; + public int EndIndex; + } + + private double[] GetResolvedKeyFrameValue(int resolvedKeyFrameIndex) => + GetResolvedKeyFrame(resolvedKeyFrameIndex).Numbers; + + private GeometryKeyFrame GetResolvedKeyFrame(int resolvedKeyFrameIndex) => + _keyFrames[_sortedResolvedKeyFrames[resolvedKeyFrameIndex]._originalKeyFrameIndex]; + + private TimeSpan LargestTimeSpanKeyTime + { + get + { + var hasTimeSpanKeyTime = false; + var largestTimeSpanKeyTime = TimeSpan.Zero; + + if (_keyFrames != null) + { + var keyFrameCount = _keyFrames.Count; + + for (var index = 0; index < keyFrameCount; index++) + { + var keyTime = _keyFrames[index].KeyTime; + + if (keyTime.Type == KeyTimeType.TimeSpan) + { + hasTimeSpanKeyTime = true; + + if (keyTime.TimeSpan > largestTimeSpanKeyTime) + { + largestTimeSpanKeyTime = keyTime.TimeSpan; + } + } + } + } + + return hasTimeSpanKeyTime ? largestTimeSpanKeyTime : TimeSpan.FromSeconds(1.0); + } + } + + private void ResolveKeyTimes() + { + var keyFrameCount = 0; + + if (_keyFrames != null) + { + keyFrameCount = _keyFrames.Count; + } + + if (keyFrameCount == 0) + { + _sortedResolvedKeyFrames = null; + _areKeyTimesValid = true; + return; + } + + _sortedResolvedKeyFrames = new ResolvedKeyFrameEntry[keyFrameCount]; + + var index = 0; + + for (; index < keyFrameCount; index++) + { + _sortedResolvedKeyFrames[index]._originalKeyFrameIndex = index; + } + + var duration = Duration; + var calculationDuration = duration.HasTimeSpan ? duration.TimeSpan : LargestTimeSpanKeyTime; + var maxKeyFrameIndex = keyFrameCount - 1; + var unspecifiedBlocks = new List(); + var hasPacedKeyTimes = false; + + index = 0; + while (index < keyFrameCount) + { + // ReSharper disable once PossibleNullReferenceException + var keyTime = _keyFrames[index].KeyTime; + + switch (keyTime.Type) + { + case KeyTimeType.Percent: + + _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.FromMilliseconds(keyTime.Percent * calculationDuration.TotalMilliseconds); + index++; + break; + + case KeyTimeType.TimeSpan: + + _sortedResolvedKeyFrames[index]._resolvedKeyTime = keyTime.TimeSpan; + + index++; + break; + + case KeyTimeType.Paced: + case KeyTimeType.Uniform: + + if (index == maxKeyFrameIndex) + { + _sortedResolvedKeyFrames[index]._resolvedKeyTime = calculationDuration; + index++; + } + else if (index == 0 && keyTime.Type == KeyTimeType.Paced) + { + _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.Zero; + index++; + } + else + { + if (keyTime.Type == KeyTimeType.Paced) + { + hasPacedKeyTimes = true; + } + + var block = new KeyTimeBlock + { + BeginIndex = index + }; + + while (++index < maxKeyFrameIndex) + { + var type = _keyFrames[index].KeyTime.Type; + + if (type == KeyTimeType.Percent || type == KeyTimeType.TimeSpan) + { + break; + } + + if (type == KeyTimeType.Paced) + { + hasPacedKeyTimes = true; + } + } + + block.EndIndex = index; + unspecifiedBlocks.Add(block); + } + + break; + } + } + + foreach (var block in unspecifiedBlocks) + { + var blockBeginTime = TimeSpan.Zero; + + if (block.BeginIndex > 0) + { + blockBeginTime = _sortedResolvedKeyFrames[block.BeginIndex - 1]._resolvedKeyTime; + } + + long segmentCount = block.EndIndex - block.BeginIndex + 1; + var uniformTimeStep = TimeSpan.FromTicks((_sortedResolvedKeyFrames[block.EndIndex]._resolvedKeyTime - blockBeginTime).Ticks / segmentCount); + + index = block.BeginIndex; + var resolvedTime = blockBeginTime + uniformTimeStep; + + while (index < block.EndIndex) + { + _sortedResolvedKeyFrames[index]._resolvedKeyTime = resolvedTime; + + resolvedTime += uniformTimeStep; + index++; + } + } + + if (hasPacedKeyTimes) + { + ResolvePacedKeyTimes(); + } + + Array.Sort(_sortedResolvedKeyFrames); + + _areKeyTimesValid = true; + } + + private void ResolvePacedKeyTimes() + { + var index = 1; + var maxKeyFrameIndex = _sortedResolvedKeyFrames.Length - 1; + + do + { + if (_keyFrames[index].KeyTime.Type == KeyTimeType.Paced) + { + var firstPacedBlockKeyFrameIndex = index; + + var segmentLengths = new List(); + var prePacedBlockKeyTime = _sortedResolvedKeyFrames[index - 1]._resolvedKeyTime; + var totalLength = 0.0; + var prevKeyValue = _keyFrames[index - 1].Numbers; + + do + { + var currentKeyValue = _keyFrames[index].Numbers; + totalLength += Math.Abs(currentKeyValue[0] - prevKeyValue[0]); + segmentLengths.Add(totalLength); + prevKeyValue = currentKeyValue; + index++; + } + while (index < maxKeyFrameIndex && _keyFrames[index].KeyTime.Type == KeyTimeType.Paced); + totalLength += Math.Abs(_keyFrames[index].Numbers[0] - prevKeyValue[0]); + + var pacedBlockDuration = _sortedResolvedKeyFrames[index]._resolvedKeyTime - prePacedBlockKeyTime; + + for (int i = 0, currentKeyFrameIndex = firstPacedBlockKeyFrameIndex; i < segmentLengths.Count; i++, currentKeyFrameIndex++) + { + _sortedResolvedKeyFrames[currentKeyFrameIndex]._resolvedKeyTime = prePacedBlockKeyTime + TimeSpan.FromMilliseconds(segmentLengths[i] / totalLength * pacedBlockDuration.TotalMilliseconds); + } + } + else + { + index++; + } + } + while (index < maxKeyFrameIndex); } } } \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrame.cs b/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrame.cs index e0a4efb4..8ada4191 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrame.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrame.cs @@ -6,6 +6,8 @@ using HandyControl.Tools; public abstract class GeometryKeyFrame : Freezable, IKeyFrame { + internal double[] Numbers; + protected GeometryKeyFrame() { @@ -14,13 +16,11 @@ public abstract class GeometryKeyFrame : Freezable, IKeyFrame protected GeometryKeyFrame(Geometry value) { AnimationHelper.DecomposeGeometryStr(value.ToString(), out var arr); - Value = arr; + Numbers = arr; + Value = value; } - protected GeometryKeyFrame(Geometry value, KeyTime keyTime) : this(value) - { - KeyTime = keyTime; - } + protected GeometryKeyFrame(Geometry value, KeyTime keyTime) : this(value) => KeyTime = keyTime; public static readonly DependencyProperty KeyTimeProperty = DependencyProperty.Register( "KeyTime", typeof(KeyTime), typeof(GeometryKeyFrame), new PropertyMetadata(KeyTime.Uniform)); @@ -34,21 +34,27 @@ public abstract class GeometryKeyFrame : Freezable, IKeyFrame object IKeyFrame.Value { get => Value; - set => Value = (double[])value; + set => Value = (Geometry)value; } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( - "Value", typeof(Geometry), typeof(GeometryKeyFrame), new PropertyMetadata(default(double[]))); + "Value", typeof(Geometry), typeof(GeometryKeyFrame), new PropertyMetadata(default(Geometry), OnValueChanged)); - public double[] Value + private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - get => (double[]) GetValue(ValueProperty); + var obj = (GeometryKeyFrame) d; + var v = (Geometry) e.NewValue; + AnimationHelper.DecomposeGeometryStr(v.ToString(), out var arr); + obj.Numbers = arr; + } + + public Geometry Value + { + get => (Geometry) GetValue(ValueProperty); set => SetValue(ValueProperty, value); } - protected string[] Strings { get; set; } - - public Geometry InterpolateValue(Geometry baseValue, double keyFrameProgress) + public double[] InterpolateValue(double[] baseValue, double keyFrameProgress) { if (keyFrameProgress < 0.0 || keyFrameProgress > 1.0) { @@ -58,5 +64,5 @@ public abstract class GeometryKeyFrame : Freezable, IKeyFrame return InterpolateValueCore(baseValue, keyFrameProgress); } - protected abstract Geometry InterpolateValueCore(Geometry baseValue, double keyFrameProgress); + protected abstract double[] InterpolateValueCore(double[] baseValue, double keyFrameProgress); } \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrameCollection.cs b/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrameCollection.cs index a65a27e4..dcf1b198 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrameCollection.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/GeometryKeyFrameCollection.cs @@ -34,7 +34,7 @@ namespace HandyControl.Media.Animation } return s_emptyCollection; - } + } } public new GeometryKeyFrameCollection Clone() => (GeometryKeyFrameCollection)base.Clone(); diff --git a/src/Shared/HandyControl_Shared/Media/Animation/LinearGeometryKeyFrame.cs b/src/Shared/HandyControl_Shared/Media/Animation/LinearGeometryKeyFrame.cs index a6ad8cb4..71e9a372 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/LinearGeometryKeyFrame.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/LinearGeometryKeyFrame.cs @@ -8,8 +8,6 @@ namespace HandyControl.Media.Animation { public class LinearGeometryKeyFrame : GeometryKeyFrame { - private double[] _baseValues; - public LinearGeometryKeyFrame() { @@ -27,13 +25,8 @@ namespace HandyControl.Media.Animation protected override Freezable CreateInstanceCore() => new LinearGeometryKeyFrame(); - protected override Geometry InterpolateValueCore(Geometry baseValue, double keyFrameProgress) + protected override double[] InterpolateValueCore(double[] baseValue, double keyFrameProgress) { - if (_baseValues == null) - { - AnimationHelper.DecomposeGeometryStr(baseValue.ToString(), out _baseValues); - } - if (MathHelper.IsVerySmall(keyFrameProgress)) { return baseValue; @@ -41,10 +34,10 @@ namespace HandyControl.Media.Animation if (MathHelper.AreClose(keyFrameProgress, 1)) { - return AnimationHelper.ComposeGeometry(Strings, Value); + return Numbers; } - return AnimationHelper.InterpolateGeometry(_baseValues, Value, keyFrameProgress, Strings); + return AnimationHelper.InterpolateGeometryValue(baseValue, Numbers, keyFrameProgress); } } } diff --git a/src/Shared/HandyControl_Shared/Media/Animation/ResolvedKeyFrameEntry.cs b/src/Shared/HandyControl_Shared/Media/Animation/ResolvedKeyFrameEntry.cs new file mode 100644 index 00000000..7531cffc --- /dev/null +++ b/src/Shared/HandyControl_Shared/Media/Animation/ResolvedKeyFrameEntry.cs @@ -0,0 +1,38 @@ +using System; + +namespace HandyControl.Media.Animation +{ + internal struct ResolvedKeyFrameEntry : IComparable + { + internal int _originalKeyFrameIndex; + + internal TimeSpan _resolvedKeyTime; + + public int CompareTo(object other) + { + var otherEntry = (ResolvedKeyFrameEntry)other; + + if (otherEntry._resolvedKeyTime > _resolvedKeyTime) + { + return -1; + } + + if (otherEntry._resolvedKeyTime < _resolvedKeyTime) + { + return 1; + } + + if (otherEntry._originalKeyFrameIndex > _originalKeyFrameIndex) + { + return -1; + } + + if (otherEntry._originalKeyFrameIndex < _originalKeyFrameIndex) + { + return 1; + } + + return 0; + } + } +} diff --git a/src/Shared/HandyControl_Shared/Media/Animation/SplineGeometryKeyFrame.cs b/src/Shared/HandyControl_Shared/Media/Animation/SplineGeometryKeyFrame.cs index d501c2db..b04ad928 100644 --- a/src/Shared/HandyControl_Shared/Media/Animation/SplineGeometryKeyFrame.cs +++ b/src/Shared/HandyControl_Shared/Media/Animation/SplineGeometryKeyFrame.cs @@ -9,8 +9,6 @@ namespace HandyControl.Media.Animation { public class SplineGeometryKeyFrame : GeometryKeyFrame { - private double[] _baseValues; - public SplineGeometryKeyFrame() { @@ -33,13 +31,8 @@ namespace HandyControl.Media.Animation protected override Freezable CreateInstanceCore() => new SplineGeometryKeyFrame(); - protected override Geometry InterpolateValueCore(Geometry baseValue, double keyFrameProgress) + protected override double[] InterpolateValueCore(double[] baseValue, double keyFrameProgress) { - if (_baseValues == null) - { - AnimationHelper.DecomposeGeometryStr(baseValue.ToString(), out _baseValues); - } - if (MathHelper.IsVerySmall(keyFrameProgress)) { return baseValue; @@ -47,11 +40,11 @@ namespace HandyControl.Media.Animation if (MathHelper.AreClose(keyFrameProgress, 1)) { - return AnimationHelper.ComposeGeometry(Strings, Value); + return Numbers; } var splineProgress = KeySpline.GetSplineProgress(keyFrameProgress); - return AnimationHelper.InterpolateGeometry(_baseValues, Value, splineProgress, Strings); + return AnimationHelper.InterpolateGeometryValue(baseValue, Numbers, splineProgress); } public static readonly DependencyProperty KeySplineProperty = DependencyProperty.Register( diff --git a/src/Shared/HandyControl_Shared/Tools/Helper/AnimationHelper.cs b/src/Shared/HandyControl_Shared/Tools/Helper/AnimationHelper.cs index 1a46a868..851a8dc1 100644 --- a/src/Shared/HandyControl_Shared/Tools/Helper/AnimationHelper.cs +++ b/src/Shared/HandyControl_Shared/Tools/Helper/AnimationHelper.cs @@ -78,5 +78,17 @@ namespace HandyControl.Tools return ComposeGeometry(strings, accumulated); } + + internal static double[] InterpolateGeometryValue(double[] from, double[] to, double progress) + { + var accumulated = new double[to.Length]; + for (var i = 0; i < to.Length; i++) + { + var fromValue = from[i]; + accumulated[i] = fromValue + (to[i] - fromValue) * progress; + } + + return accumulated; + } } } \ No newline at end of file