Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for customizing gradient values using GradientValueProvider #2182

Merged
merged 2 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions Sources/Private/CoreAnimation/Animations/LayerProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ struct CustomizableProperty<ValueRepresentation> {

/// 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
Expand All @@ -77,6 +80,7 @@ enum PropertyName: String, CaseIterable {
case position = "Position"
case rotation = "Rotation"
case strokeWidth = "Stroke Width"
case gradientColors = "Colors"
}

// MARK: CALayer properties
Expand Down Expand Up @@ -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<CGPoint> {
Expand All @@ -291,7 +295,7 @@ extension CustomizableProperty {
static var color: CustomizableProperty<CGColor> {
.init(
name: [.color],
conversion: { typeErasedValue in
conversion: { typeErasedValue, _ in
guard let color = typeErasedValue as? LottieColor else {
return nil
}
Expand All @@ -303,7 +307,7 @@ extension CustomizableProperty {
static var opacity: CustomizableProperty<CGFloat> {
.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
Expand All @@ -316,7 +320,7 @@ extension CustomizableProperty {
static var scaleX: CustomizableProperty<CGFloat> {
.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
Expand All @@ -329,7 +333,7 @@ extension CustomizableProperty {
static var scaleY: CustomizableProperty<CGFloat> {
.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
Expand All @@ -342,7 +346,7 @@ extension CustomizableProperty {
static var rotation: CustomizableProperty<CGFloat> {
.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
Expand All @@ -355,13 +359,34 @@ extension CustomizableProperty {
static var position: CustomizableProperty<CGPoint> {
.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<CGFloat> {
.init(
name: name,
conversion: { typeErasedValue in
conversion: { typeErasedValue, _ in
guard let vector = typeErasedValue as? LottieVector1D else { return nil }
return vector.cgFloatValue
})
Expand Down
6 changes: 4 additions & 2 deletions Sources/Private/CoreAnimation/ValueProviderStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value>? 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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class GradientFillProperties: NodePropertyMap, KeypathSearchable {
PropertyName.opacity.rawValue : opacity,
"Start Point" : startPoint,
"End Point" : endPoint,
Comment on lines 28 to 29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also add these 2 to the PropertyName enum for consistency?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I've only been defining properties in the PropertyName enum once they're used by both rendering engines. There's a ton of string values currently defined throughout the main thread rendering engine and it seems like a good idea to avoid touching them until / unless necessary

"Colors" : colors,
PropertyName.gradientColors.rawValue : colors,
]
properties = Array(keypathProperties.values)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ final class GradientStrokeProperties: NodePropertyMap, KeypathSearchable {
PropertyName.opacity.rawValue : opacity,
"Start Point" : startPoint,
"End Point" : endPoint,
Comment on lines 48 to 49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dito

"Colors" : colors,
PropertyName.gradientColors.rawValue : colors,
PropertyName.strokeWidth.rawValue : width,
"Dashes" : dashPattern,
"Dash Phase" : dashPhase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions Tests/Samples/Issues/issue_1854.json
Original file line number Diff line number Diff line change
@@ -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":[]}
10 changes: 10 additions & 0 deletions Tests/SnapshotConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Supports Core Animation engine
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.