private void RotateWidgets(Vector2 pivotPoint, List<Widget> widgets, Vector2 curMousePos, Vector2 prevMousePos,
			bool snapped, List<Tuple<Widget, AccumulativeRotationHelper>> accumulativeRotationHelpers)
		{
			WidgetTransformsHelper.ApplyTransformationToWidgetsGroupObb(
				sv.Scene,
				widgets,
				widgets.Count <= 1 ? (Vector2?) null : pivotPoint, widgets.Count <= 1,
				curMousePos, prevMousePos,
				false,
				(originalVectorInObbSpace, deformedVectorInObbSpace) => {

					double rotation = 0;
					if (originalVectorInObbSpace.Length > Mathf.ZeroTolerance &&
						deformedVectorInObbSpace.Length > Mathf.ZeroTolerance) {
						rotation = Mathd.Wrap180(deformedVectorInObbSpace.Atan2Deg - originalVectorInObbSpace.Atan2Deg);
					}

					if (snapped) {
						rotation = WidgetTransformsHelper.RoundTo(rotation, 15);
					}

					foreach (Tuple<Widget, AccumulativeRotationHelper> tuple in accumulativeRotationHelpers) {
						tuple.Item2.Rotate((float) rotation);
					}

					return new Transform2d(Vector2d.Zero, Vector2d.One, rotation);
				}
			);

			foreach (Tuple<Widget, AccumulativeRotationHelper> tuple in accumulativeRotationHelpers) {
				SetAnimableProperty.Perform(tuple.Item1, nameof(Widget.Rotation), tuple.Item2.Rotation,
					CoreUserPreferences.Instance.AutoKeyframes);
			}
		}
Exemple #2
0
        private void ScaleKeyframes()
        {
            var boundaries = GridSelection.GetSelectionBoundaries();

            if (boundaries.HasValue || Scale < Mathf.ZeroTolerance)
            {
                var saved = new List <IKeyframe>();
                for (int i = boundaries.Value.Top; i <= boundaries.Value.Bottom; ++i)
                {
                    if (!(Document.Current.Rows[i].Components.Get <NodeRow>()?.Node is IAnimationHost animable))
                    {
                        continue;
                    }
                    foreach (var animator in animable.Animators.ToList())
                    {
                        saved.Clear();
                        IEnumerable <IKeyframe> keys = animator.ReadonlyKeys.Where(k =>
                                                                                   k.Frame >= boundaries.Value.Left && k.Frame < boundaries.Value.Right
                                                                                   ).ToList();
                        if (Scale < 1)
                        {
                            keys = keys.Reverse().ToList();
                        }
                        foreach (var key in keys)
                        {
                            saved.Add(key);
                            RemoveKeyframe.Perform(animator, key.Frame);
                        }
                        foreach (var key in saved)
                        {
                            // The formula should behave similiar to stretching animation with mouse
                            int newFrame = (int)(
                                boundaries.Value.Left +
                                (key.Frame - boundaries.Value.Left) *
                                (1 + (boundaries.Value.Left - boundaries.Value.Right) * Scale) /
                                (1 + boundaries.Value.Left - boundaries.Value.Right)
                                );
                            var newKey = key.Clone();
                            newKey.Frame = newFrame;
                            SetAnimableProperty.Perform(
                                animable, animator.TargetPropertyPath, newKey.Value,
                                createAnimatorIfNeeded: true,
                                createInitialKeyframeForNewAnimator: false,
                                newKey.Frame
                                );
                            SetKeyframe.Perform(animable, animator.TargetPropertyPath, Document.Current.AnimationId, newKey);
                        }
                    }
                }
                ClearGridSelection.Perform();
                for (int i = boundaries.Value.Top; i <= boundaries.Value.Bottom; ++i)
                {
                    SelectGridSpan.Perform(i, boundaries.Value.Left, (int)(boundaries.Value.Left + (boundaries.Value.Right - boundaries.Value.Left) * Scale));
                }
            }
            else
            {
                Document.Current.History.RollbackTransaction();
            }
        }
 private void ScaleKeyframes()
 {
     if (GridSelection.GetSelectionBoundaries(out var boundaries) && Scale > Mathf.ZeroTolerance)
     {
         var processed = new HashSet <IAnimator>();
         var saved     = new List <IKeyframe>();
         foreach (var animable in GridSelection.EnumerateAnimators(boundaries))
         {
             foreach (var animator in animable.Animators)
             {
                 if (animator.AnimationId != Document.Current.AnimationId || processed.Contains(animator))
                 {
                     continue;
                 }
                 processed.Add(animator);
                 saved.Clear();
                 IEnumerable <IKeyframe> keys = animator.ReadonlyKeys.Where(k =>
                                                                            k.Frame >= boundaries.Left && k.Frame < boundaries.Right
                                                                            ).ToList();
                 if (Scale < 1)
                 {
                     keys = keys.Reverse().ToList();
                 }
                 foreach (var key in keys)
                 {
                     saved.Add(key);
                     RemoveKeyframe.Perform(animator, key.Frame);
                 }
                 foreach (var key in saved)
                 {
                     // The formula should behave similiar to stretching animation with mouse
                     int newFrame = (int)(
                         boundaries.Left +
                         (key.Frame - boundaries.Left) *
                         (1 + (boundaries.Left - boundaries.Right) * Scale) /
                         (1 + boundaries.Left - boundaries.Right)
                         );
                     var newKey = key.Clone();
                     newKey.Frame = newFrame;
                     SetAnimableProperty.Perform(
                         animable.Host, animator.TargetPropertyPath, newKey.Value,
                         createAnimatorIfNeeded: true,
                         createInitialKeyframeForNewAnimator: false,
                         newKey.Frame
                         );
                     SetKeyframe.Perform(animable.Host, animator.TargetPropertyPath, Document.Current.AnimationId, newKey);
                 }
             }
         }
         ClearGridSelection.Perform();
         for (int i = boundaries.Top; i <= boundaries.Bottom; ++i)
         {
             SelectGridSpan.Perform(i, boundaries.Left, (int)(boundaries.Left + (boundaries.Right - boundaries.Left) * Scale));
         }
     }
        private void RotateWidgets(Vector2 pivotPoint, List <Widget> widgets, Vector2 curMousePos, Vector2 prevMousePos,
                                   bool discret, ref float accumAngle, ref float prevAngle)
        {
            List <KeyValuePair <Widget, float> > wasRotations = widgets.Select(widget => new KeyValuePair <Widget, float>(widget, widget.Rotation)).ToList();

            float rotationRes = prevAngle;

            Utils.ApplyTransformationToWidgetsGroupOobb(
                sv.Scene,
                widgets, pivotPoint, false, curMousePos, prevMousePos,
                (originalVectorInOobbSpace, deformedVectorInOobbSpace) => {
                float rotation = 0;
                if (originalVectorInOobbSpace.Length > Mathf.ZeroTolerance &&
                    deformedVectorInOobbSpace.Length > Mathf.ZeroTolerance)
                {
                    rotation = Mathf.Wrap180(deformedVectorInOobbSpace.Atan2Deg - originalVectorInOobbSpace.Atan2Deg);
                }

                if (discret)
                {
                    rotation = Utils.RoundTo(rotation, 15);
                }

                rotationRes = rotation;

                return(Matrix32.Rotation(rotation * Mathf.DegToRad));
            }
                );

            // accumulate rotation, each visual turn of widget will increase it's angle on 360,
            // without that code angle will be allways [-180; 180)
            rotationRes = rotationRes.NormalizeRotation();
            float rotationDelta = (rotationRes - prevAngle).NormalizeRotation();

            prevAngle = rotationRes;

            accumAngle += rotationDelta;

            foreach (KeyValuePair <Widget, float> wasRotation in wasRotations)
            {
                SetAnimableProperty.Perform(wasRotation.Key, nameof(Widget.Rotation), wasRotation.Value + accumAngle);
            }
        }
Exemple #5
0
        public static void SetAnimatorAndInitialKeyframeIfNeed(IAnimable animable, params string[] properties)
        {
            var frame = Document.Current.AnimationFrame;

            Document.Current.AnimationFrame = 0;
            IAnimator animator;

            foreach (var propName in properties)
            {
                if (!animable.Animators.TryFind(propName, out animator, Document.Current.AnimationId))
                {
                    var propValue = animable.GetType().GetProperty(propName).GetValue(animable);
                    SetProperty.Perform(animable, propName, propValue);
                    var type = animable.GetType().GetProperty(propName).PropertyType;
                    animator = AnimatorRegistry.Instance.CreateAnimator(type);
                    animator.TargetProperty = propName;
                    animator.AnimationId    = Document.Current.AnimationId;
                    SetAnimator.Perform(animable, animator);
                    SetAnimableProperty.Perform(animable, propName, propValue);
                }
            }
            Document.Current.AnimationFrame = frame;
        }
Exemple #6
0
        public static void ApplyTransformationToWidgetsGroupOobb(IEnumerable <Widget> widgetsInParentSpace,
                                                                 Widget parentWidget, Matrix32 oobbInParentSpace, Matrix32 oobbTransformation)
        {
            Matrix32 originalOobbToWorldSpace = oobbInParentSpace * parentWidget.LocalToWorldTransform;

            foreach (Widget widget in widgetsInParentSpace)
            {
                Matrix32 widgetToOriginalOobbSpace = widget.LocalToWorldTransform * originalOobbToWorldSpace.CalcInversed();

                // calculate new oobb transformation in world space
                Matrix32 deformedOobbToWorldSpace = oobbTransformation * originalOobbToWorldSpace;

                Matrix32 deformedWidgetToWorldSpace = widgetToOriginalOobbSpace * deformedOobbToWorldSpace;

                Matrix32 deformedWidgetToParentSpace =
                    deformedWidgetToWorldSpace * widget.ParentWidget.LocalToWorldTransform.CalcInversed();

                Transform2 widgetResultTransform = widget.CalcApplicableTransfrom2(deformedWidgetToParentSpace);

                // correct rotation delta, to prevent wrong values if new angle 0 and previous is 359,
                // then rotationDelta must be 1
                float rotationDelta = (widget.Rotation - widgetResultTransform.Rotation).NormalizeRotation();

                if ((widget.Position - widgetResultTransform.Translation).Length > Mathf.ZeroTolerance)
                {
                    SetAnimableProperty.Perform(widget, nameof(Widget.Position), widgetResultTransform.Translation);
                }
                if (Mathf.Abs(rotationDelta) > Mathf.ZeroTolerance)
                {
                    SetAnimableProperty.Perform(widget, nameof(Widget.Rotation), widget.Rotation + rotationDelta);
                }
                if ((widget.Scale - widgetResultTransform.Scale).Length > Mathf.ZeroTolerance)
                {
                    SetAnimableProperty.Perform(widget, nameof(Widget.Scale), widgetResultTransform.Scale);
                }
            }
        }
Exemple #7
0
 private static void SetKeyframes(Dictionary <Node, BoneAnimationData> keyframeDictionary)
 {
     foreach (var pair in keyframeDictionary)
     {
         if (pair.Value.NoParentKeyframes)
         {
             TransformPropertyAndKeyframes(pair.Key, nameof(Bone.Position), pair.Value.PositionTransformer);
         }
         else
         {
             SetProperty.Perform(pair.Key, nameof(Bone.Position), pair.Value.CurrentPosition);
             SetProperty.Perform(pair.Key, nameof(Bone.Rotation), pair.Value.CurrentRotation);
             foreach (var keyframe in pair.Value.PositionKeyframes)
             {
                 SetKeyframe.Perform(pair.Key, nameof(Bone.Position), Document.Current.AnimationId, keyframe.Value);
             }
             foreach (var keyframe in pair.Value.RotationKeyframes)
             {
                 SetKeyframe.Perform(pair.Key, nameof(Bone.Rotation), Document.Current.AnimationId, keyframe.Value);
             }
             SetAnimableProperty.Perform(pair.Key, nameof(Bone.BaseIndex), 0);
         }
     }
 }
        private void Stretch(IntRectangle boundaries, DragSide side, int newPos, bool stretchMarkers)
        {
            int length;

            if (side == DragSide.Left)
            {
                length = boundaries.Right - newPos - 1;
            }
            else
            {
                length = newPos - boundaries.Left - 1;
            }
            int oldLength = boundaries.Right - boundaries.Left - 1;
            var processed = new HashSet <IAnimator>();

            foreach (var animable in GridSelection.EnumerateAnimators(boundaries))
            {
                foreach (var animator in animable.Animators)
                {
                    if (animator.AnimationId != Document.Current.AnimationId || processed.Contains(animator) || !savedKeyframes.ContainsKey(animator))
                    {
                        continue;
                    }
                    processed.Add(animator);
                    IEnumerable <IKeyframe> saved = savedKeyframes[animator];
                    if (
                        side == DragSide.Left && length < oldLength ||
                        side == DragSide.Right && length > oldLength
                        )
                    {
                        saved = saved.Reverse();
                    }
                    foreach (var key in saved)
                    {
                        RemoveKeyframe.Perform(animator, key.Frame);
                    }
                    foreach (var key in saved)
                    {
                        double relpos = savedPositions[key];
                        int    newFrame;
                        if (side == DragSide.Left)
                        {
                            newFrame = (int)Math.Round(newPos + relpos * length);
                        }
                        else
                        {
                            newFrame = (int)Math.Round(boundaries.Left + relpos * length);
                        }
                        var newKey = key.Clone();
                        newKey.Frame = newFrame;
                        SetAnimableProperty.Perform(
                            animable.Host, animator.TargetPropertyPath, newKey.Value,
                            createAnimatorIfNeeded: true,
                            createInitialKeyframeForNewAnimator: false,
                            newKey.Frame
                            );
                        SetKeyframe.Perform(animable.Host, animator.TargetPropertyPath, Document.Current.AnimationId, newKey);
                    }
                }
            }
            if (stretchMarkers)
            {
                foreach (var marker in savedMarkers)
                {
                    DeleteMarker.Perform(marker, removeDependencies: false);
                }
                foreach (var marker in savedMarkers)
                {
                    double relpos = savedMarkerPositions[marker];
                    int    newFrame;
                    if (side == DragSide.Left)
                    {
                        newFrame = (int)Math.Round(newPos + relpos * length);
                    }
                    else
                    {
                        newFrame = (int)Math.Round(boundaries.Left + relpos * length);
                    }
                    var newMarker = marker.Clone();
                    newMarker.Frame = newFrame;
                    SetMarker.Perform(newMarker, removeDependencies: false);
                }
            }
        }
Exemple #9
0
        public static void Perform(IntVector2 offset, bool removeOriginals)
        {
            var processedKeys = new HashSet <IKeyframe>();
            var operations    = new List <Action>();

            foreach (var row in Document.Current.Rows)
            {
                var spans = row.Components.GetOrAdd <GridSpanListComponent>().Spans.GetNonOverlappedSpans(offset.X > 0);
                foreach (var span in spans)
                {
                    var node = row.Components.Get <NodeRow>()?.Node ?? row.Components.Get <PropertyRow>()?.Node;
                    if (node == null)
                    {
                        continue;
                    }
                    var property = row.Components.Get <PropertyRow>()?.Animator.TargetPropertyPath;
                    foreach (var a in node.Animators.ToList())
                    {
                        if (property != null && a.TargetPropertyPath != property)
                        {
                            continue;
                        }
                        IEnumerable <IKeyframe> keysEnumerable = a.Keys.Where(k => k.Frame >= span.A && k.Frame < span.B);
                        if (offset.X > 0)
                        {
                            keysEnumerable = keysEnumerable.Reverse();
                        }
                        foreach (var k in keysEnumerable)
                        {
                            if (processedKeys.Contains(k))
                            {
                                continue;
                            }
                            processedKeys.Add(k);
                            var destRow = row.Index + offset.Y;
                            if (!CheckRowRange(destRow))
                            {
                                continue;
                            }
                            var destRowComponents = Document.Current.Rows[destRow].Components;
                            var destNode          = destRowComponents.Get <NodeRow>()?.Node ?? destRowComponents.Get <PropertyRow>()?.Node;
                            if (destNode == null || !ArePropertiesCompatible(node, destNode, a.TargetPropertyPath))
                            {
                                continue;
                            }
                            if (k.Frame + offset.X >= 0)
                            {
                                var k1 = k.Clone();
                                k1.Frame += offset.X;
                                // The same logic is used to create keyframes as everywhere, but extended by setting
                                // all parameters from a particular keyframe. Yes, this creates some overhead.
                                operations.Add(() => SetAnimableProperty.Perform(destNode, a.TargetPropertyPath, k1.Value, true, false, k1.Frame));
                                operations.Add(() => SetKeyframe.Perform(destNode, a.TargetPropertyPath, Document.Current.AnimationId, k1));
                            }
                            // Order is importent. RemoveKeyframe must be after SetKeyframe,
                            // to prevent animator clean up if all keys were removed.
                            if (removeOriginals)
                            {
                                operations.Add(() => RemoveKeyframe.Perform(a, k.Frame));
                            }
                        }
                    }
                }
            }
            foreach (var o in operations)
            {
                o();
            }
        }
Exemple #10
0
        public static void ApplyTransformationToWidgetsGroupObb(IEnumerable <Widget> widgetsInParentSpace,
                                                                Matrix32d obbInParentSpace, Transform2d obbTransformation, bool convertScaleToSize)
        {
            Matrix32d originalObbToParentSpace = obbInParentSpace;

            if (Math.Abs(originalObbToParentSpace.CalcDeterminant()) < Mathf.ZeroTolerance)
            {
                return;
            }

            Matrix32d obbTransformationMatrix = obbTransformation.ToMatrix32();

            foreach (Widget widget in widgetsInParentSpace)
            {
                WidgetZeroScalePreserver zeroScalePreserver = new WidgetZeroScalePreserver(widget);
                zeroScalePreserver.Store();
                try {
                    Matrix32d widgetToParentSpace      = widget.CalcLocalToParentTransformDouble();
                    Matrix32d widgetToOriginalObbSpace = widgetToParentSpace * originalObbToParentSpace.CalcInversed();

                    // Calculate the new obb transformation in the parent space.
                    Matrix32d deformedObbToParentSpace = obbTransformationMatrix * originalObbToParentSpace;

                    Matrix32d deformedWidgetToParentSpace = widgetToOriginalObbSpace * deformedObbToParentSpace;

                    Transform2d widgetResultTransform = widget.ExtractTransform2Double(deformedWidgetToParentSpace,
                                                                                       widget.Rotation + obbTransformation.Rotation);

                    // Correct a rotation delta, to prevent wrong values if a new angle 0 and previous is 359,
                    // then rotationDelta must be 1.
                    double rotationDelta = Mathd.Wrap180(widgetResultTransform.Rotation - widget.Rotation);

                    // Reduce an influence of small transformations (Scale, Position, Rotation).
                    bool needChangeScaleX = IsSignificantChangeOfValue(widget.Scale.X, widgetResultTransform.Scale.X);
                    bool needChangeScaleY = IsSignificantChangeOfValue(widget.Scale.Y, widgetResultTransform.Scale.Y);

                    if (needChangeScaleX || needChangeScaleY)
                    {
                        Vector2 useScale = new Vector2(
                            (float)(!needChangeScaleX ? widget.Scale.X : widgetResultTransform.Scale.X),
                            (float)(!needChangeScaleY ? widget.Scale.Y : widgetResultTransform.Scale.Y)
                            );
                        useScale = zeroScalePreserver.AdjustToScale(useScale);

                        zeroScalePreserver.Restore();

                        if (!convertScaleToSize)
                        {
                            SetAnimableProperty.Perform(widget, nameof(Widget.Scale), useScale,
                                                        CoreUserPreferences.Instance.AutoKeyframes);
                        }
                        else
                        {
                            Vector2 useSize = new Vector2(
                                Math.Abs(widget.Scale.X) < FloatSignificantDelta
                                                                        ? widget.Size.X
                                                                        : widget.Size.X * Math.Abs(useScale.X / widget.Scale.X),
                                Math.Abs(widget.Scale.Y) < FloatSignificantDelta
                                                                        ? widget.Size.Y
                                                                        : widget.Size.Y * Math.Abs(useScale.Y / widget.Scale.Y)
                                );
                            SetAnimableProperty.Perform(widget, nameof(Widget.Size), useSize,
                                                        CoreUserPreferences.Instance.AutoKeyframes);
                        }
                    }

                    bool needChangePositionX = IsSignificantChangeOfValue(widget.Position.X, widgetResultTransform.Translation.X);
                    bool needChangePositionY = IsSignificantChangeOfValue(widget.Position.Y, widgetResultTransform.Translation.Y);

                    if (needChangePositionX || needChangePositionY)
                    {
                        SetAnimableProperty.Perform(widget, nameof(Widget.Position),
                                                    new Vector2(
                                                        (float)(!needChangePositionX ? widget.Position.X : widgetResultTransform.Translation.X),
                                                        (float)(!needChangePositionY ? widget.Position.Y : widgetResultTransform.Translation.Y)
                                                        ),
                                                    CoreUserPreferences.Instance.AutoKeyframes);
                    }

                    if (IsSignificantChangeByDelta(widget.Rotation, rotationDelta))
                    {
                        SetAnimableProperty.Perform(widget, nameof(Widget.Rotation), (float)(widget.Rotation + rotationDelta),
                                                    CoreUserPreferences.Instance.AutoKeyframes);
                    }
                } finally {
                    zeroScalePreserver.Restore();
                }
            }
        }
Exemple #11
0
            protected override bool ProbeInternal(BoneRow node, Row row, RowLocation location)
            {
                if (!(location.ParentRow.Components.Contains <BoneRow>() || location.ParentRow.Components.Contains <FolderRow>()))
                {
                    return(false);
                }
                var targetParent = location.ParentRow.Components.Get <BoneRow>()?.Bone;

                try {
                    var bone = row.Components.Get <BoneRow>().Bone;
                    // Check if bone target parent is bone descendant
                    if (IsDescendant(bone, targetParent))
                    {
                        return(false);
                    }
                    if (bone.BaseIndex == 0 && !location.ParentRow.Components.Contains <BoneRow>())
                    {
                        MoveFolderItemTo(bone, location);
                    }
                    else if (!location.ParentRow.Components.Contains <BoneRow>())
                    {
                        SetAnimableProperty.Perform(
                            bone, nameof(Bone.Position),
                            bone.Position * bone.CalcLocalToParentWidgetTransform(),
                            CoreUserPreferences.Instance.AutoKeyframes);
                        var   boneEntry = bone.Parent.AsWidget.BoneArray[bone.Index];
                        float angle     = (boneEntry.Tip - boneEntry.Joint).Atan2Deg;
                        SetAnimableProperty.Perform(
                            bone, nameof(Bone.Rotation),
                            angle, CoreUserPreferences.Instance.AutoKeyframes);
                    }
                    else
                    {
                        SetAnimableProperty.Perform(
                            bone, nameof(Bone.Position),
                            Vector2.Zero, CoreUserPreferences.Instance.AutoKeyframes);
                        var   newParent   = location.ParentRow.Components.Get <BoneRow>().Bone;
                        var   parentEntry = newParent.Parent.AsWidget.BoneArray[newParent.Index];
                        float parentAngle = (parentEntry.Tip - parentEntry.Joint).Atan2Deg;
                        var   boneEntry   = bone.Parent.AsWidget.BoneArray[bone.Index];
                        float angle       = (boneEntry.Tip - boneEntry.Joint).Atan2Deg;
                        SetAnimableProperty.Perform(
                            bone, nameof(Bone.Rotation),
                            angle - parentAngle, CoreUserPreferences.Instance.AutoKeyframes);
                    }

                    SetProperty.Perform(bone, nameof(Bone.BaseIndex), targetParent?.Index ?? 0);
                    SortBonesInChain.Perform(bone);
                    var nodes      = Document.Current.Container.Nodes;
                    var parentBone = nodes.GetBone(bone.BaseIndex);
                    while (parentBone != null && !parentBone.EditorState().ChildrenExpanded)
                    {
                        SetProperty.Perform(parentBone.EditorState(), nameof(NodeEditorState.ChildrenExpanded), true);
                        bone = nodes.GetBone(bone.BaseIndex);
                    }
                } catch (InvalidOperationException e) {
                    AlertDialog.Show(e.Message);
                    return(false);
                }
                return(true);
            }
        private void Stretch(Boundaries boundaries, DragSide side, int newPos, bool stretchMarkers)
        {
            int length;

            if (side == DragSide.Left)
            {
                length = boundaries.Right - newPos - 1;
            }
            else
            {
                length = newPos - boundaries.Left - 1;
            }
            int oldLength = boundaries.Right - boundaries.Left - 1;

            for (int i = boundaries.Top; i <= boundaries.Bottom; ++i)
            {
                if (!(Document.Current.Rows[i].Components.Get <NodeRow>()?.Node is IAnimationHost animable))
                {
                    continue;
                }
                foreach (var animator in animable.Animators.ToList())
                {
                    IEnumerable <IKeyframe> saved = savedKeyframes[animator];
                    if (
                        side == DragSide.Left && length < oldLength ||
                        side == DragSide.Right && length > oldLength
                        )
                    {
                        saved = saved.Reverse();
                    }
                    foreach (var key in saved)
                    {
                        RemoveKeyframe.Perform(animator, key.Frame);
                    }
                    foreach (var key in saved)
                    {
                        double relpos = savedPositions[key];
                        int    newFrame;
                        if (side == DragSide.Left)
                        {
                            newFrame = (int)Math.Round(newPos + relpos * length);
                        }
                        else
                        {
                            newFrame = (int)Math.Round(boundaries.Left + relpos * length);
                        }
                        var newKey = key.Clone();
                        newKey.Frame = newFrame;
                        SetAnimableProperty.Perform(
                            animable, animator.TargetPropertyPath, newKey.Value,
                            createAnimatorIfNeeded: true,
                            createInitialKeyframeForNewAnimator: false,
                            newKey.Frame
                            );
                        SetKeyframe.Perform(animable, animator.TargetPropertyPath, Document.Current.AnimationId, newKey);
                    }
                }
            }
            if (stretchMarkers)
            {
                foreach (var marker in savedMarkers)
                {
                    DeleteMarker.Perform(Document.Current.Container, marker, removeDependencies: false);
                }
                foreach (var marker in savedMarkers)
                {
                    double relpos = savedMarkerPositions[marker];
                    int    newFrame;
                    if (side == DragSide.Left)
                    {
                        newFrame = (int)Math.Round(newPos + relpos * length);
                    }
                    else
                    {
                        newFrame = (int)Math.Round(boundaries.Left + relpos * length);
                    }
                    var newMarker = marker.Clone();
                    newMarker.Frame = newFrame;
                    SetMarker.Perform(Document.Current.Container, newMarker, removeDependencies: false);
                }
            }
        }