Handle rounded rectangles more accurately (#288)
* Rename to match After Effects and some initial refactoring. * Correctly support roundness where size and roundness are not animated. * Handle non-static rounded rectangles better. * Also fixes a misunderstanding with how animations are stopped in Composition.
This commit is contained in:
Родитель
29682524ff
Коммит
8432ee254d
|
@ -18,8 +18,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
|
|||
Animatable<double> rotation,
|
||||
Animatable<double> innerRadius,
|
||||
Animatable<double> outerRadius,
|
||||
Animatable<double> innerRoundedness,
|
||||
Animatable<double> outerRoundedness)
|
||||
Animatable<double> innerRoundness,
|
||||
Animatable<double> outerRoundness)
|
||||
: base(in args, drawingDirection)
|
||||
{
|
||||
StarType = starType;
|
||||
|
@ -28,8 +28,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
|
|||
Rotation = rotation;
|
||||
InnerRadius = innerRadius;
|
||||
OuterRadius = outerRadius;
|
||||
InnerRoundedness = innerRoundedness;
|
||||
OuterRoundedness = outerRoundedness;
|
||||
InnerRoundness = innerRoundness;
|
||||
OuterRoundness = outerRoundness;
|
||||
}
|
||||
|
||||
internal PolyStarType StarType { get; }
|
||||
|
@ -44,9 +44,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
|
|||
|
||||
internal Animatable<double> OuterRadius { get; }
|
||||
|
||||
internal Animatable<double> InnerRoundedness { get; }
|
||||
internal Animatable<double> InnerRoundness { get; }
|
||||
|
||||
internal Animatable<double> OuterRoundedness { get; }
|
||||
internal Animatable<double> OuterRoundness { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ShapeContentType ContentType => ShapeContentType.Polystar;
|
||||
|
|
|
@ -14,15 +14,22 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
|
|||
DrawingDirection drawingDirection,
|
||||
IAnimatableVector3 position,
|
||||
IAnimatableVector3 size,
|
||||
Animatable<double> cornerRadius)
|
||||
Animatable<double> roundness)
|
||||
: base(in args, drawingDirection)
|
||||
{
|
||||
Position = position;
|
||||
Size = size;
|
||||
CornerRadius = cornerRadius;
|
||||
Roundness = roundness;
|
||||
}
|
||||
|
||||
public Animatable<double> CornerRadius { get; }
|
||||
/// <summary>
|
||||
/// Determines how round the corners of the rectangle are. If the rectangle
|
||||
/// is a square and the roundness is equal to half of the width then the
|
||||
/// rectangle will be rendered as a circle. Once the roundness value reaches
|
||||
/// half of the minimum of the shortest dimension, increasing it has no
|
||||
/// further effect.
|
||||
/// </summary>
|
||||
public Animatable<double> Roundness { get; }
|
||||
|
||||
public IAnimatableVector3 Size { get; }
|
||||
|
||||
|
|
|
@ -17,6 +17,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
|
|||
Radius = radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the rounding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the shape to which this applies is a rectangle, the rounding will
|
||||
/// only apply if the rectangle has a 0 roundness value. Once the radius
|
||||
/// value reaches half of the largest dimension of the rectangle, the
|
||||
/// result will be equivalent to an ellipse of the same size, and
|
||||
/// increasing the radius further will have no further effect.
|
||||
/// </remarks>
|
||||
public Animatable<double> Radius { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
@ -616,7 +616,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
|
|||
|
||||
yield return FromAnimatable(nameof(content.Size), content.Size);
|
||||
yield return FromAnimatable(nameof(content.Position), content.Position);
|
||||
yield return FromAnimatable(nameof(content.CornerRadius), content.CornerRadius);
|
||||
yield return FromAnimatable(nameof(content.Roundness), content.Roundness);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -644,7 +644,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
|
|||
var result = superclassContent;
|
||||
result.Add(nameof(content.Size), FromAnimatable(content.Size));
|
||||
result.Add(nameof(content.Position), FromAnimatable(content.Position));
|
||||
result.Add(nameof(content.CornerRadius), FromAnimatable(content.CornerRadius));
|
||||
result.Add(nameof(content.Roundness), FromAnimatable(content.Roundness));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -326,23 +326,23 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
|
|||
var position = ReadAnimatableVector3(obj.ObjectPropertyOrNull("p"));
|
||||
var rotation = ReadAnimatableFloat(obj.ObjectPropertyOrNull("r"));
|
||||
var outerRadius = ReadAnimatableFloat(obj.ObjectPropertyOrNull("or"));
|
||||
var outerRoundedness = ReadAnimatableFloat(obj.ObjectPropertyOrNull("os"));
|
||||
var outerRoundness = ReadAnimatableFloat(obj.ObjectPropertyOrNull("os"));
|
||||
|
||||
var polystarType = SyToPolystarType(obj.DoublePropertyOrNull("sy")) ?? Polystar.PolyStarType.Polygon;
|
||||
|
||||
Animatable<double> innerRadius;
|
||||
Animatable<double> innerRoundedness;
|
||||
Animatable<double> innerRoundness;
|
||||
|
||||
switch (polystarType)
|
||||
{
|
||||
case Polystar.PolyStarType.Star:
|
||||
innerRadius = ReadAnimatableFloat(obj.ObjectPropertyOrNull("ir"));
|
||||
innerRoundedness = ReadAnimatableFloat(obj.ObjectPropertyOrNull("is"));
|
||||
innerRoundness = ReadAnimatableFloat(obj.ObjectPropertyOrNull("is"));
|
||||
break;
|
||||
|
||||
default:
|
||||
innerRadius = null;
|
||||
innerRoundedness = null;
|
||||
innerRoundness = null;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -356,8 +356,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
|
|||
rotation,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
innerRoundedness,
|
||||
outerRoundedness);
|
||||
innerRoundness,
|
||||
outerRoundness);
|
||||
}
|
||||
|
||||
Rectangle ReadRectangle(
|
||||
|
@ -370,10 +370,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
|
|||
var drawingDirection = DToDrawingDirection(obj.DoublePropertyOrNull("d"));
|
||||
var position = ReadAnimatableVector3(obj.ObjectPropertyOrNull("p"));
|
||||
var size = ReadAnimatableVector3(obj.ObjectPropertyOrNull("s"));
|
||||
var cornerRadius = ReadAnimatableFloat(obj.ObjectPropertyOrNull("r"));
|
||||
var roundness = ReadAnimatableFloat(obj.ObjectPropertyOrNull("r"));
|
||||
|
||||
obj.AssertAllPropertiesRead();
|
||||
return new Rectangle(in shapeLayerContentArgs, drawingDirection, position, size, cornerRadius);
|
||||
return new Rectangle(in shapeLayerContentArgs, drawingDirection, position, size, roundness);
|
||||
}
|
||||
|
||||
Path ReadPath(
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
internal static readonly Vector2 MyPosition = MyVector2("Position");
|
||||
internal static readonly Vector2 MySize = MyVector2("Size");
|
||||
internal static readonly Matrix3x2 MyTransformMatrix = MyMatrix3x2("TransformMatrix");
|
||||
static readonly Scalar MyRoundness = MyScalar("Roundness");
|
||||
static readonly Scalar MyTStart = MyScalar("TStart");
|
||||
static readonly Scalar MyTEnd = MyScalar("TEnd");
|
||||
|
||||
|
@ -67,6 +68,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
|
||||
internal static Scalar RootScalar(string propertyName) => Scalar(RootProperty(propertyName));
|
||||
|
||||
internal static Vector2 ConstrainedCornerRadiusScalar(double roundness)
|
||||
=> Vector2(Min(roundness, Min(MySize.X, MySize.Y) / 2), Min(roundness, Min(MySize.X, MySize.Y) / 2));
|
||||
|
||||
internal static Vector2 ConstrainedCornerRadiusScalar()
|
||||
=> Vector2(Min(MyRoundness, Min(MySize.X, MySize.Y) / 2), Min(MyRoundness, Min(MySize.X, MySize.Y) / 2));
|
||||
|
||||
internal static Vector2 ConstrainedCornerRadiusScalar(Sn.Vector2 size)
|
||||
=> Vector2(Min(MyRoundness, Math.Min(size.X, size.Y) / 2), Min(MyRoundness, Math.Min(size.X, size.Y) / 2));
|
||||
|
||||
// The value of a Color property stored as a Vector4 on the theming property set.
|
||||
static Vector4 ThemedColor4Property(string propertyName) => Vector4(ThemeProperty(propertyName));
|
||||
|
||||
|
|
|
@ -2152,7 +2152,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
var size = context.TrimAnimatable(rectangle.Size);
|
||||
|
||||
// If a Rectangle is in the context, use it to override the corner radius.
|
||||
var cornerRadius = context.TrimAnimatable(shapeContext.RoundedCorner != null ? shapeContext.RoundedCorner.Radius : rectangle.CornerRadius);
|
||||
var cornerRadius = context.TrimAnimatable(shapeContext.RoundedCorner != null ? shapeContext.RoundedCorner.Radius : rectangle.Roundness);
|
||||
|
||||
if (position.IsAnimated || size.IsAnimated || cornerRadius.IsAnimated)
|
||||
{
|
||||
|
@ -2231,141 +2231,199 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
|
||||
CompositionShape TranslateRectangleContent(TranslationContext context, ShapeContentContext shapeContext, Rectangle shapeContent)
|
||||
{
|
||||
var compositionRectangle = _c.CreateSpriteShape();
|
||||
var result = _c.CreateSpriteShape();
|
||||
var position = context.TrimAnimatable(shapeContent.Position);
|
||||
var size = context.TrimAnimatable(shapeContent.Size);
|
||||
|
||||
if (shapeContent.CornerRadius.AlwaysEquals(0) && shapeContext.RoundedCorner is null)
|
||||
if (shapeContent.Roundness.AlwaysEquals(0) && shapeContext.RoundedCorner is null)
|
||||
{
|
||||
CompositionGeometry geometry;
|
||||
TranslateAndApplyNonRoundedRectangleContent(
|
||||
context,
|
||||
shapeContext,
|
||||
shapeContent,
|
||||
position,
|
||||
size,
|
||||
result);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateAndApplyRoundedRectangleContent(
|
||||
context,
|
||||
shapeContext,
|
||||
shapeContent,
|
||||
position,
|
||||
size,
|
||||
result);
|
||||
}
|
||||
|
||||
// Use a non-rounded rectangle geometry.
|
||||
if (_targetUapVersion <= 7)
|
||||
return result;
|
||||
}
|
||||
|
||||
void TranslateAndApplyNonRoundedRectangleContent(
|
||||
TranslationContext context,
|
||||
ShapeContentContext shapeContext,
|
||||
Rectangle shapeContent,
|
||||
in TrimmedAnimatable<Vector3> position,
|
||||
in TrimmedAnimatable<Vector3> size,
|
||||
CompositionSpriteShape compositionShape)
|
||||
{
|
||||
Debug.Assert(shapeContent.Roundness.AlwaysEquals(0) && shapeContext.RoundedCorner is null, "Precondition");
|
||||
|
||||
CompositionGeometry geometry;
|
||||
|
||||
// Use a non-rounded rectangle geometry.
|
||||
if (_targetUapVersion <= 7)
|
||||
{
|
||||
// V7 did not reliably draw non-rounded rectangles.
|
||||
// Work around the problem by using a rounded rectangle with a tiny corner radius.
|
||||
var roundedRectangleGeometry = _c.CreateRoundedRectangleGeometry();
|
||||
geometry = roundedRectangleGeometry;
|
||||
|
||||
// NOTE: magic tiny corner radius number - do not change!
|
||||
roundedRectangleGeometry.CornerRadius = new Sn.Vector2(0.000001F);
|
||||
|
||||
roundedRectangleGeometry.Offset = InitialOffset(size: size, position: position);
|
||||
|
||||
if (!size.IsAnimated)
|
||||
{
|
||||
// V7 did not reliably draw non-rounded rectangles.
|
||||
// Work around the problem by using a rounded rectangle with a tiny corner radius.
|
||||
var roundedRectangleGeometry = _c.CreateRoundedRectangleGeometry();
|
||||
geometry = roundedRectangleGeometry;
|
||||
|
||||
// NOTE: magic tiny corner radius number - do not change!
|
||||
roundedRectangleGeometry.CornerRadius = new Sn.Vector2(0.000001F);
|
||||
|
||||
// Convert size and position into offset. This is necessary because a geometry's offset is for
|
||||
// its top left corner, wherease a Lottie position is for its centerpoint.
|
||||
roundedRectangleGeometry.Offset = Vector2(position.InitialValue - (size.InitialValue / 2));
|
||||
|
||||
if (!size.IsAnimated)
|
||||
{
|
||||
roundedRectangleGeometry.Size = Vector2(size.InitialValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// V8 and beyond doesn't need the rounded rectangle workaround.
|
||||
var rectangleGeometry = _c.CreateRectangleGeometry();
|
||||
geometry = rectangleGeometry;
|
||||
|
||||
// Convert size and position into offset. This is necessary because a geometry's offset is for
|
||||
// its top left corner, wherease a Lottie position is for its centerpoint.
|
||||
rectangleGeometry.Offset = Vector2(position.InitialValue - (size.InitialValue / 2));
|
||||
|
||||
if (!size.IsAnimated)
|
||||
{
|
||||
rectangleGeometry.Size = Vector2(size.InitialValue);
|
||||
}
|
||||
}
|
||||
|
||||
compositionRectangle.Geometry = geometry;
|
||||
|
||||
if (position.IsAnimated || size.IsAnimated)
|
||||
{
|
||||
Expr offsetExpression;
|
||||
if (position.IsAnimated)
|
||||
{
|
||||
ApplyVector2KeyFrameAnimation(context, position, geometry, nameof(Rectangle.Position));
|
||||
geometry.Properties.InsertVector2(nameof(Rectangle.Position), Vector2(position.InitialValue));
|
||||
if (size.IsAnimated)
|
||||
{
|
||||
// Size AND position are animated.
|
||||
offsetExpression = ExpressionFactory.PositionAndSizeToOffsetExpression;
|
||||
ApplyVector2KeyFrameAnimation(context, size, geometry, nameof(Rectangle.Size));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Position is animated
|
||||
offsetExpression = ExpressionFactory.HalfSizeToOffsetExpression(Vector2(size.InitialValue / 2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Size is animated.
|
||||
offsetExpression = ExpressionFactory.PositionToOffsetExpression(Vector2(position.InitialValue));
|
||||
ApplyVector2KeyFrameAnimation(context, size, geometry, nameof(Rectangle.Size));
|
||||
}
|
||||
|
||||
var offsetExpressionAnimation = _c.CreateExpressionAnimation(offsetExpression);
|
||||
offsetExpressionAnimation.SetReferenceParameter("my", geometry);
|
||||
StartExpressionAnimation(geometry, "Offset", offsetExpressionAnimation);
|
||||
roundedRectangleGeometry.Size = Vector2(size.InitialValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use a rounded rectangle geometry.
|
||||
var geometry = _c.CreateRoundedRectangleGeometry();
|
||||
compositionRectangle.Geometry = geometry;
|
||||
|
||||
// If a RoundedRectangle is in the context, use it to override the corner radius.
|
||||
var cornerRadius = context.TrimAnimatable(shapeContext.RoundedCorner != null ? shapeContext.RoundedCorner.Radius : shapeContent.CornerRadius);
|
||||
if (cornerRadius.IsAnimated)
|
||||
{
|
||||
ApplyScalarKeyFrameAnimation(context, cornerRadius, geometry, "CornerRadius.X");
|
||||
ApplyScalarKeyFrameAnimation(context, cornerRadius, geometry, "CornerRadius.Y");
|
||||
}
|
||||
else
|
||||
{
|
||||
geometry.CornerRadius = Vector2((float)cornerRadius.InitialValue);
|
||||
}
|
||||
// V8 and beyond doesn't need the rounded rectangle workaround.
|
||||
var rectangleGeometry = _c.CreateRectangleGeometry();
|
||||
geometry = rectangleGeometry;
|
||||
|
||||
// Convert size and position into offset. This is necessary because a geometry's offset is for
|
||||
// its top left corner, wherease a Lottie position is for its centerpoint.
|
||||
geometry.Offset = Vector2(position.InitialValue - (size.InitialValue / 2));
|
||||
// its top left corner, whereas a Lottie position is for its centerpoint.
|
||||
rectangleGeometry.Offset = InitialOffset(size: size, position: position);
|
||||
|
||||
if (!size.IsAnimated)
|
||||
{
|
||||
geometry.Size = Vector2(size.InitialValue);
|
||||
rectangleGeometry.Size = Vector2(size.InitialValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (position.IsAnimated || size.IsAnimated)
|
||||
compositionShape.Geometry = geometry;
|
||||
|
||||
ApplyRectangleContentCommon(context, shapeContext, shapeContent, compositionShape, size, position, geometry);
|
||||
}
|
||||
|
||||
void TranslateAndApplyRoundedRectangleContent(
|
||||
TranslationContext context,
|
||||
ShapeContentContext shapeContext,
|
||||
Rectangle shapeContent,
|
||||
in TrimmedAnimatable<Vector3> position,
|
||||
in TrimmedAnimatable<Vector3> size,
|
||||
CompositionSpriteShape compositionShape)
|
||||
{
|
||||
// Use a rounded rectangle geometry.
|
||||
var geometry = _c.CreateRoundedRectangleGeometry();
|
||||
compositionShape.Geometry = geometry;
|
||||
|
||||
// If a RoundedRectangle is in the context, use it to override the roundness unless the roundness is non-0.
|
||||
var cornerRadius = context.TrimAnimatable(
|
||||
shapeContext.RoundedCorner != null && shapeContent.Roundness.AlwaysEquals(0)
|
||||
? shapeContext.RoundedCorner.Radius
|
||||
: shapeContent.Roundness);
|
||||
|
||||
// In After Effects, the rectangle Roundness has no further effect once it reaches min(Size.X, Size.Y)/2.
|
||||
// In Composition, the cornerRadius continues to affect the shape even beyond min(Size.X, Size.Y)/2.
|
||||
// If size or corner radius are animated, handle this with an expression.
|
||||
if (cornerRadius.IsAnimated || size.IsAnimated)
|
||||
{
|
||||
if (cornerRadius.IsAnimated)
|
||||
{
|
||||
Expr offsetExpression;
|
||||
if (position.IsAnimated)
|
||||
{
|
||||
ApplyVector2KeyFrameAnimation(context, position, geometry, nameof(Rectangle.Position));
|
||||
geometry.Properties.InsertScalar("Roundness", Float(cornerRadius.InitialValue));
|
||||
ApplyScalarKeyFrameAnimation(context, cornerRadius, geometry.Properties, "Roundness");
|
||||
|
||||
geometry.Properties.InsertVector2(nameof(Rectangle.Position), Vector2(position.InitialValue));
|
||||
if (size.IsAnimated)
|
||||
{
|
||||
// Size AND position are animated.
|
||||
offsetExpression = ExpressionFactory.PositionAndSizeToOffsetExpression;
|
||||
ApplyVector2KeyFrameAnimation(context, size, geometry, nameof(Rectangle.Size));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Position is animated
|
||||
offsetExpression = ExpressionFactory.HalfSizeToOffsetExpression(Vector2(size.InitialValue / 2));
|
||||
}
|
||||
if (size.IsAnimated)
|
||||
{
|
||||
// Both size and cornerRadius are animated.
|
||||
var cornerRadiusExpression = _c.CreateExpressionAnimation(ConstrainedCornerRadiusScalar());
|
||||
cornerRadiusExpression.SetReferenceParameter("my", geometry);
|
||||
StartExpressionAnimation(geometry, "CornerRadius", cornerRadiusExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Size is animated.
|
||||
offsetExpression = ExpressionFactory.PositionToOffsetExpression(Vector2(position.InitialValue));
|
||||
// Only the cornerRadius is animated.
|
||||
var cornerRadiusExpression = _c.CreateExpressionAnimation(ConstrainedCornerRadiusScalar(Vector2(size.InitialValue)));
|
||||
cornerRadiusExpression.SetReferenceParameter("my", geometry);
|
||||
StartExpressionAnimation(geometry, "CornerRadius", cornerRadiusExpression);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only the size is animated.
|
||||
var cornerRadiusExpression = _c.CreateExpressionAnimation(ConstrainedCornerRadiusScalar(cornerRadius.InitialValue));
|
||||
cornerRadiusExpression.SetReferenceParameter("my", geometry);
|
||||
StartExpressionAnimation(geometry, "CornerRadius", cornerRadiusExpression);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Static size and corner radius.
|
||||
var cornerRadiusValue = Math.Min(cornerRadius.InitialValue, Math.Min(size.InitialValue.X, size.InitialValue.Y) / 2);
|
||||
geometry.CornerRadius = Vector2((float)cornerRadiusValue);
|
||||
}
|
||||
|
||||
geometry.Offset = InitialOffset(size:size, position:position);
|
||||
|
||||
if (!size.IsAnimated)
|
||||
{
|
||||
geometry.Size = Vector2(size.InitialValue);
|
||||
}
|
||||
|
||||
ApplyRectangleContentCommon(context, shapeContext, shapeContent, compositionShape, size, position, geometry);
|
||||
}
|
||||
|
||||
// Convert the size and position for a geometry into an offset.
|
||||
// This is necessary because a geometry's offset describes its
|
||||
// top left corner, whereas a Lottie position describes its centerpoint.
|
||||
static Sn.Vector2 InitialOffset(
|
||||
in TrimmedAnimatable<Vector3> size,
|
||||
in TrimmedAnimatable<Vector3> position)
|
||||
=> Vector2(position.InitialValue - (size.InitialValue / 2));
|
||||
|
||||
void ApplyRectangleContentCommon(
|
||||
TranslationContext context,
|
||||
ShapeContentContext shapeContext,
|
||||
Rectangle shapeContent,
|
||||
CompositionSpriteShape compositionRectangle,
|
||||
in TrimmedAnimatable<Vector3> size,
|
||||
in TrimmedAnimatable<Vector3> position,
|
||||
CompositionGeometry geometry)
|
||||
{
|
||||
if (position.IsAnimated || size.IsAnimated)
|
||||
{
|
||||
Expr offsetExpression;
|
||||
if (position.IsAnimated)
|
||||
{
|
||||
ApplyVector2KeyFrameAnimation(context, position, geometry, nameof(Rectangle.Position));
|
||||
geometry.Properties.InsertVector2(nameof(Rectangle.Position), Vector2(position.InitialValue));
|
||||
if (size.IsAnimated)
|
||||
{
|
||||
// Size AND position are animated.
|
||||
offsetExpression = ExpressionFactory.PositionAndSizeToOffsetExpression;
|
||||
ApplyVector2KeyFrameAnimation(context, size, geometry, nameof(Rectangle.Size));
|
||||
}
|
||||
|
||||
var offsetExpressionAnimation = _c.CreateExpressionAnimation(offsetExpression);
|
||||
offsetExpressionAnimation.SetReferenceParameter("my", geometry);
|
||||
StartExpressionAnimation(geometry, nameof(geometry.Offset), offsetExpressionAnimation);
|
||||
else
|
||||
{
|
||||
// Only Position is animated
|
||||
offsetExpression = ExpressionFactory.HalfSizeToOffsetExpression(Vector2(size.InitialValue / 2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Size is animated.
|
||||
offsetExpression = ExpressionFactory.PositionToOffsetExpression(Vector2(position.InitialValue));
|
||||
ApplyVector2KeyFrameAnimation(context, size, geometry, nameof(Rectangle.Size));
|
||||
}
|
||||
|
||||
var offsetExpressionAnimation = _c.CreateExpressionAnimation(offsetExpression);
|
||||
offsetExpressionAnimation.SetReferenceParameter("my", geometry);
|
||||
StartExpressionAnimation(geometry, "Offset", offsetExpressionAnimation);
|
||||
}
|
||||
|
||||
// Lottie rectangles have 0,0 at top right. That causes problems for TrimPath which expects 0,0 to be top left.
|
||||
|
@ -2386,6 +2444,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
var width = size.InitialValue.X;
|
||||
var height = size.InitialValue.Y;
|
||||
var trimOffsetDegrees = (width / (2 * (width + height))) * 360;
|
||||
|
||||
TranslateAndApplyShapeContentContext(
|
||||
context,
|
||||
shapeContext,
|
||||
|
@ -2398,8 +2457,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
Describe(compositionRectangle, shapeContent.Name);
|
||||
Describe(compositionRectangle.Geometry, $"{shapeContent.Name}.RectangleGeometry");
|
||||
}
|
||||
|
||||
return compositionRectangle;
|
||||
}
|
||||
|
||||
void CheckForRoundedCornersOnPath(TranslationContext context, ShapeContentContext shapeContext)
|
||||
|
|
|
@ -141,22 +141,24 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData
|
|||
/// <param name="propertyName">The name of the property.</param>
|
||||
public void StopAnimation(string propertyName)
|
||||
{
|
||||
// We also need to stop animations on any sub-channels and super-channels.
|
||||
// For example, if the property is TransformMatrix we must also stop animations
|
||||
// on TransformMatrix.M11, TransformMatrix.M12, etc; and if the property is
|
||||
// TransformMatrix.M11 we must also stop animations on TransformMatrix,
|
||||
// TransformMatrix.M12, etc.
|
||||
//
|
||||
// We also need to stop animations on any sub-channels and the root property.
|
||||
// Examples:
|
||||
// Sub-channels: stopping Offset must also stop
|
||||
// Offset.X, Offset.Y, etc..
|
||||
// Root property: stopping Offset.X must also
|
||||
// stop Offset.
|
||||
// If there's a dot in the name it is a sub-channel name.
|
||||
var subChannelPrefix = $"{propertyName}.";
|
||||
var firstDotIndex = propertyName.IndexOf('.');
|
||||
var rootPropertyPrefix = $"{(firstDotIndex >= 0 ? propertyName.Substring(0, firstDotIndex) : propertyName)}.";
|
||||
var rootPropertyName = $"{(firstDotIndex >= 0 ? propertyName.Substring(0, firstDotIndex) : string.Empty)}.";
|
||||
|
||||
for (var i = 0; i < _animators.Count; i++)
|
||||
{
|
||||
var animatorPropertyName = _animators[i].AnimatedProperty;
|
||||
|
||||
if (animatorPropertyName == propertyName ||
|
||||
animatorPropertyName.StartsWith(rootPropertyPrefix))
|
||||
animatorPropertyName == rootPropertyName ||
|
||||
animatorPropertyName.StartsWith(subChannelPrefix))
|
||||
{
|
||||
_animators.RemoveAt(i);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче