diff --git a/Sources/Private/CoreAnimation/Animations/LayerProperty.swift b/Sources/Private/CoreAnimation/Animations/LayerProperty.swift index d2ee60b839..a48c48279d 100644 --- a/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +++ b/Sources/Private/CoreAnimation/Animations/LayerProperty.swift @@ -62,7 +62,10 @@ struct CustomizableProperty { /// A closure that coverts the type-erased value of an `AnyValueProvider` /// to the strongly-typed representation used by this property, if possible. - let conversion: (Any) -> ValueRepresentation? + /// - `value` is the value for the current frame that should be converted, + /// as returned by `AnyValueProvider.typeErasedStorage`. + /// - `valueProvider` is the `AnyValueProvider` that returned the type-erased value. + let conversion: (_ value: Any, _ valueProvider: AnyValueProvider) -> ValueRepresentation? } // MARK: - PropertyName @@ -77,6 +80,7 @@ enum PropertyName: String, CaseIterable { case position = "Position" case rotation = "Rotation" case strokeWidth = "Stroke Width" + case gradientColors = "Colors" } // MARK: CALayer properties @@ -260,14 +264,14 @@ extension LayerProperty { .init( caLayerKeypath: #keyPath(CAGradientLayer.colors), defaultValue: nil, - customizableProperty: nil /* currently unsupported */ ) + customizableProperty: .gradientColors) } static var locations: LayerProperty<[CGFloat]> { .init( caLayerKeypath: #keyPath(CAGradientLayer.locations), defaultValue: nil, - customizableProperty: nil /* currently unsupported */ ) + customizableProperty: .gradientLocations) } static var startPoint: LayerProperty { @@ -291,7 +295,7 @@ extension CustomizableProperty { static var color: CustomizableProperty { .init( name: [.color], - conversion: { typeErasedValue in + conversion: { typeErasedValue, _ in guard let color = typeErasedValue as? LottieColor else { return nil } @@ -303,7 +307,7 @@ extension CustomizableProperty { static var opacity: CustomizableProperty { .init( name: [.opacity], - conversion: { typeErasedValue in + conversion: { typeErasedValue, _ in guard let vector = typeErasedValue as? LottieVector1D else { return nil } // Lottie animation files express opacity as a numerical percentage value @@ -316,7 +320,7 @@ extension CustomizableProperty { static var scaleX: CustomizableProperty { .init( name: [.scale], - conversion: { typeErasedValue in + conversion: { typeErasedValue, _ in guard let vector = typeErasedValue as? LottieVector3D else { return nil } // Lottie animation files express scale as a numerical percentage value @@ -329,7 +333,7 @@ extension CustomizableProperty { static var scaleY: CustomizableProperty { .init( name: [.scale], - conversion: { typeErasedValue in + conversion: { typeErasedValue, _ in guard let vector = typeErasedValue as? LottieVector3D else { return nil } // Lottie animation files express scale as a numerical percentage value @@ -342,7 +346,7 @@ extension CustomizableProperty { static var rotation: CustomizableProperty { .init( name: [.rotation], - conversion: { typeErasedValue in + conversion: { typeErasedValue, _ in guard let vector = typeErasedValue as? LottieVector1D else { return nil } // Lottie animation files express rotation in degrees @@ -355,13 +359,34 @@ extension CustomizableProperty { static var position: CustomizableProperty { .init( name: [.position], - conversion: { ($0 as? LottieVector3D)?.pointValue }) + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector3D else { return nil } + return vector.pointValue + }) + } + + static var gradientColors: CustomizableProperty<[CGColor]> { + .init( + name: [.gradientColors], + conversion: { _, typeErasedValueProvider in + guard let gradientValueProvider = typeErasedValueProvider as? GradientValueProvider else { return nil } + return gradientValueProvider.colors.map { $0.cgColorValue } + }) + } + + static var gradientLocations: CustomizableProperty<[CGFloat]> { + .init( + name: [.gradientColors], + conversion: { _, typeErasedValueProvider in + guard let gradientValueProvider = typeErasedValueProvider as? GradientValueProvider else { return nil } + return gradientValueProvider.locations.map { CGFloat($0) } + }) } static func floatValue(_ name: PropertyName...) -> CustomizableProperty { .init( name: name, - conversion: { typeErasedValue in + conversion: { typeErasedValue, _ in guard let vector = typeErasedValue as? LottieVector1D else { return nil } return vector.cgFloatValue }) diff --git a/Sources/Private/CoreAnimation/ValueProviderStore.swift b/Sources/Private/CoreAnimation/ValueProviderStore.swift index fadaedb6fa..68a1432ca4 100644 --- a/Sources/Private/CoreAnimation/ValueProviderStore.swift +++ b/Sources/Private/CoreAnimation/ValueProviderStore.swift @@ -73,9 +73,11 @@ final class ValueProviderStore { // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe? in - guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value) else { + guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value, anyValueProvider) else { logger.assertionFailure(""" - Could not convert value of type \(type(of: typeErasedKeyframe.value)) to expected type \(Value.self) + Could not convert value of type \(type(of: typeErasedKeyframe.value)) from \(anyValueProvider) to expected type \( + Value + .self) """) return nil } diff --git a/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift b/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift index f227d1e8b8..97e37ef614 100644 --- a/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +++ b/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift @@ -27,7 +27,7 @@ final class GradientFillProperties: NodePropertyMap, KeypathSearchable { PropertyName.opacity.rawValue : opacity, "Start Point" : startPoint, "End Point" : endPoint, - "Colors" : colors, + PropertyName.gradientColors.rawValue : colors, ] properties = Array(keypathProperties.values) } diff --git a/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift b/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift index bcb808e223..62f0dfe6c7 100644 --- a/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +++ b/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift @@ -47,7 +47,7 @@ final class GradientStrokeProperties: NodePropertyMap, KeypathSearchable { PropertyName.opacity.rawValue : opacity, "Start Point" : startPoint, "End Point" : endPoint, - "Colors" : colors, + PropertyName.gradientColors.rawValue : colors, PropertyName.strokeWidth.rawValue : width, "Dashes" : dashPattern, "Dash Phase" : dashPhase, diff --git a/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift b/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift index b200129cc4..1a837ada52 100644 --- a/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +++ b/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift @@ -69,16 +69,18 @@ public final class GradientValueProvider: ValueProvider { } public var storage: ValueProviderStorage<[Double]> { - .closure { [self] frame in - hasUpdate = false + if let block = block { + return .closure { [self] frame in + hasUpdate = false - if let block = block { let newColors = block(frame) let newLocations = locationsBlock?(frame) ?? [] value = value(from: newColors, locations: newLocations) - } - return value + return value + } + } else { + return .singleValue(value) } } @@ -129,7 +131,9 @@ public final class GradientValueProvider: ValueProvider { } -extension GradientValueProvider { +// MARK: Equatable + +extension GradientValueProvider: Equatable { public static func ==(_ lhs: GradientValueProvider, _ rhs: GradientValueProvider) -> Bool { lhs.identity == rhs.identity } diff --git a/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift b/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift index cfafa6c03f..e7e799b92e 100644 --- a/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +++ b/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift @@ -74,7 +74,9 @@ public final class PointValueProvider: ValueProvider { private let identity: AnyHashable } -extension PointValueProvider { +// MARK: Equatable + +extension PointValueProvider: Equatable { public static func ==(_ lhs: PointValueProvider, _ rhs: PointValueProvider) -> Bool { lhs.identity == rhs.identity } diff --git a/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift b/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift index 242238e1a0..e993a3e3bf 100644 --- a/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +++ b/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift @@ -74,7 +74,9 @@ public final class SizeValueProvider: ValueProvider { private let identity: AnyHashable } -extension SizeValueProvider { +// MARK: Equatable + +extension SizeValueProvider: Equatable { public static func ==(_ lhs: SizeValueProvider, _ rhs: SizeValueProvider) -> Bool { lhs.identity == rhs.identity } diff --git a/Tests/Samples/Issues/issue_1854.json b/Tests/Samples/Issues/issue_1854.json new file mode 100644 index 0000000000..d67ae7bd7f --- /dev/null +++ b/Tests/Samples/Issues/issue_1854.json @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":68,"w":450,"h":450,"nm":"GX_fab_rings","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".accent","cl":"accent","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[100]},{"i":{"x":[0.724],"y":[1]},"o":{"x":[0.305],"y":[0]},"t":32,"s":[80]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":52,"s":[80]},{"t":66,"s":[0]}],"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[225,225,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.537,0.537,0.667],"y":[1.001,1.001,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[13.667,13.667,100]},{"i":{"x":[0.538,0.538,0.667],"y":[0.938,0.938,1]},"o":{"x":[0.586,0.586,0.333],"y":[0.015,0.015,0]},"t":16,"s":[90.667,90.667,100]},{"i":{"x":[0.472,0.472,0.667],"y":[1.057,1.057,1]},"o":{"x":[0.264,0.264,0.333],"y":[-0.013,-0.013,0]},"t":32,"s":[67.712,67.712,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":50,"s":[74.667,74.667,100]},{"i":{"x":[0.284,0.284,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":52,"s":[74.667,74.667,100]},{"t":66,"s":[5.667,5.667,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[450,450],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.98,0.118,0.306,0.5,0.98,0.118,0.306,1,0.98,0.118,0.306,0,1,0.5,0.75,1,0.5],"ix":9}},"s":{"a":0,"k":[0,-180],"ix":5},"e":{"a":0,"k":[0,212],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false}],"ip":0,"op":68,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Tests/SnapshotConfiguration.swift b/Tests/SnapshotConfiguration.swift index 1edc7e3909..31338c244b 100644 --- a/Tests/SnapshotConfiguration.swift +++ b/Tests/SnapshotConfiguration.swift @@ -97,6 +97,16 @@ extension SnapshotConfiguration { AnimationKeypath(keypath: "**.base_color.**.Color"): ColorValueProvider(.black), ]), + "Issues/issue_1854": .customValueProviders([ + AnimationKeypath(keypath: "**.Colors"): GradientValueProvider( + [ + LottieColor(r: 0, g: 0, b: 0, a: 0), + LottieColor(r: 1, g: 1, b: 1, a: 0.5), + LottieColor(r: 1, g: 1, b: 1, a: 1), + ], + locations: [0, 0.3, 1.0]), + ]), + "Issues/issue_1847": .customValueProviders([ AnimationKeypath(keypath: "**.Stroke 1.**.Color"): ColorValueProvider(.red), ]), diff --git a/Tests/__Snapshots__/AutomaticEngineTests/testAutomaticEngineDetection.Issues-issue_1854.txt b/Tests/__Snapshots__/AutomaticEngineTests/testAutomaticEngineDetection.Issues-issue_1854.txt new file mode 100644 index 0000000000..4b816d7353 --- /dev/null +++ b/Tests/__Snapshots__/AutomaticEngineTests/testAutomaticEngineDetection.Issues-issue_1854.txt @@ -0,0 +1 @@ +Supports Core Animation engine \ No newline at end of file diff --git a/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-0.png b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-0.png new file mode 100644 index 0000000000..3ae5ad435f Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-0.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-100.png b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-100.png new file mode 100644 index 0000000000..3ae5ad435f Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-100.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-25.png b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-25.png new file mode 100644 index 0000000000..832b6aa49d Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-25.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-50.png b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-50.png new file mode 100644 index 0000000000..c7c2517111 Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-50.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-75.png b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-75.png new file mode 100644 index 0000000000..726c698671 Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testCoreAnimationRenderingEngine.Issues-issue_1854-75.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-0.png b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-0.png new file mode 100644 index 0000000000..3ae5ad435f Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-0.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-100.png b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-100.png new file mode 100644 index 0000000000..3ae5ad435f Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-100.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-25.png b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-25.png new file mode 100644 index 0000000000..1015e39f73 Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-25.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-50.png b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-50.png new file mode 100644 index 0000000000..3620c97f92 Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-50.png differ diff --git a/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-75.png b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-75.png new file mode 100644 index 0000000000..72b2bc06a9 Binary files /dev/null and b/Tests/__Snapshots__/SnapshotTests/testMainThreadRenderingEngine.Issues-issue_1854-75.png differ