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 Rounded Corners #1795

Merged
merged 14 commits into from
Nov 2, 2022
26 changes: 25 additions & 1 deletion Lottie.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,15 @@
2EAF5B0527A0798700E00531 /* AnimationFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */; };
2EAF5B0627A0798700E00531 /* AnimationFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */; };
36E57EAC28AF7ADF00B7EFDA /* HardcodedTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E57EAB28AF7ADF00B7EFDA /* HardcodedTextProvider.swift */; };
57210913291073E400169699 /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57210912291073E400169699 /* RoundedCorners.swift */; };
57210914291073E400169699 /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57210912291073E400169699 /* RoundedCorners.swift */; };
57210915291073E400169699 /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57210912291073E400169699 /* RoundedCorners.swift */; };
5721091B2910874A00169699 /* RoundedCornersNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5721091A2910874A00169699 /* RoundedCornersNode.swift */; };
5721091C2910874A00169699 /* RoundedCornersNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5721091A2910874A00169699 /* RoundedCornersNode.swift */; };
5721091D2910874A00169699 /* RoundedCornersNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5721091A2910874A00169699 /* RoundedCornersNode.swift */; };
5721091F29119F3100169699 /* BezierPathRoundExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5721091E29119F3100169699 /* BezierPathRoundExtension.swift */; };
5721092029119F3100169699 /* BezierPathRoundExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5721091E29119F3100169699 /* BezierPathRoundExtension.swift */; };
5721092129119F3100169699 /* BezierPathRoundExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5721091E29119F3100169699 /* BezierPathRoundExtension.swift */; };
6C4877FF28FF20140005AF07 /* DotLottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4877E228FF20140005AF07 /* DotLottieAnimation.swift */; };
6C48780028FF20140005AF07 /* DotLottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4877E228FF20140005AF07 /* DotLottieAnimation.swift */; };
6C48780128FF20140005AF07 /* DotLottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4877E228FF20140005AF07 /* DotLottieAnimation.swift */; };
Expand Down Expand Up @@ -844,6 +853,9 @@
2EAF59F027A0798700E00531 /* PointValueProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointValueProvider.swift; sourceTree = "<group>"; };
2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationFontProvider.swift; sourceTree = "<group>"; };
36E57EAB28AF7ADF00B7EFDA /* HardcodedTextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcodedTextProvider.swift; sourceTree = "<group>"; };
57210912291073E400169699 /* RoundedCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorners.swift; sourceTree = "<group>"; };
5721091A2910874A00169699 /* RoundedCornersNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornersNode.swift; sourceTree = "<group>"; };
5721091E29119F3100169699 /* BezierPathRoundExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierPathRoundExtension.swift; sourceTree = "<group>"; };
6C4877E228FF20140005AF07 /* DotLottieAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotLottieAnimation.swift; sourceTree = "<group>"; };
6C4877E328FF20140005AF07 /* DotLottieFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotLottieFile.swift; sourceTree = "<group>"; };
6C4877E428FF20140005AF07 /* DotLottieUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotLottieUtils.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -995,6 +1007,7 @@
2E9C95302822F43000677516 /* Stroke.swift */,
2E9C95312822F43000677516 /* Rectangle.swift */,
2E9C95322822F43000677516 /* Star.swift */,
57210912291073E400169699 /* RoundedCorners.swift */,
);
path = ShapeItems;
sourceTree = "<group>";
Expand Down Expand Up @@ -1141,6 +1154,7 @@
isa = PBXGroup;
children = (
2E9C95682822F43000677516 /* TrimPathNode.swift */,
5721091A2910874A00169699 /* RoundedCornersNode.swift */,
);
path = ModifierNodes;
sourceTree = "<group>";
Expand Down Expand Up @@ -1379,6 +1393,7 @@
2E9C95CC2822F43100677516 /* CurveVertex.swift */,
2E9C95CD2822F43100677516 /* VectorsExtensions.swift */,
6DB3BDBB28245A14002A276D /* CGPointExtension.swift */,
5721091E29119F3100169699 /* BezierPathRoundExtension.swift */,
);
path = Primitives;
sourceTree = "<group>";
Expand Down Expand Up @@ -1900,6 +1915,7 @@
2E9C97022822F43100677516 /* PreCompLayer.swift in Sources */,
2E9C96EA2822F43100677516 /* SolidLayer.swift in Sources */,
2EAF5AA127A0798700E00531 /* AnimationSubview.macOS.swift in Sources */,
57210913291073E400169699 /* RoundedCorners.swift in Sources */,
2E9C96C62822F43100677516 /* GroupInterpolator.swift in Sources */,
2E9C96F02822F43100677516 /* TransformLayer.swift in Sources */,
2E9C96332822F43100677516 /* Font.swift in Sources */,
Expand All @@ -1914,6 +1930,7 @@
2E9C974A2822F43100677516 /* CGColor+RGB.swift in Sources */,
2E9C96572822F43100677516 /* ShapeCompositionLayer.swift in Sources */,
2E9C96F32822F43100677516 /* AnimationLayer.swift in Sources */,
5721091B2910874A00169699 /* RoundedCornersNode.swift in Sources */,
2E9C95FA2822F43100677516 /* Star.swift in Sources */,
2E9C961E2822F43100677516 /* KeyedDecodingContainerExtensions.swift in Sources */,
2E9C96512822F43100677516 /* PreCompositionLayer.swift in Sources */,
Expand Down Expand Up @@ -1991,6 +2008,7 @@
2E9C96A82822F43100677516 /* FillNode.swift in Sources */,
2EAF5ACB27A0798700E00531 /* LottieAnimationViewBase.swift in Sources */,
2E9C96CC2822F43100677516 /* ShapeRenderLayer.swift in Sources */,
5721091F29119F3100169699 /* BezierPathRoundExtension.swift in Sources */,
6C48780228FF20140005AF07 /* DotLottieFile.swift in Sources */,
2EAF5AEC27A0798700E00531 /* LottieLogger.swift in Sources */,
2E9C976E2822F43100677516 /* KeyframeExtensions.swift in Sources */,
Expand Down Expand Up @@ -2132,6 +2150,7 @@
2E9C97032822F43100677516 /* PreCompLayer.swift in Sources */,
2E9C96EB2822F43100677516 /* SolidLayer.swift in Sources */,
2EAF5AA227A0798700E00531 /* AnimationSubview.macOS.swift in Sources */,
57210914291073E400169699 /* RoundedCorners.swift in Sources */,
2E9C96C72822F43100677516 /* GroupInterpolator.swift in Sources */,
2E9C96F12822F43100677516 /* TransformLayer.swift in Sources */,
2E9C96342822F43100677516 /* Font.swift in Sources */,
Expand All @@ -2146,6 +2165,7 @@
2E9C974B2822F43100677516 /* CGColor+RGB.swift in Sources */,
2E9C96582822F43100677516 /* ShapeCompositionLayer.swift in Sources */,
2E9C96F42822F43100677516 /* AnimationLayer.swift in Sources */,
5721091C2910874A00169699 /* RoundedCornersNode.swift in Sources */,
2E9C95FB2822F43100677516 /* Star.swift in Sources */,
2E9C961F2822F43100677516 /* KeyedDecodingContainerExtensions.swift in Sources */,
2E9C96522822F43100677516 /* PreCompositionLayer.swift in Sources */,
Expand Down Expand Up @@ -2223,6 +2243,7 @@
2E9C96A92822F43100677516 /* FillNode.swift in Sources */,
2EAF5ACC27A0798700E00531 /* LottieAnimationViewBase.swift in Sources */,
2E9C96CD2822F43100677516 /* ShapeRenderLayer.swift in Sources */,
5721092029119F3100169699 /* BezierPathRoundExtension.swift in Sources */,
6C48780328FF20140005AF07 /* DotLottieFile.swift in Sources */,
2EAF5AED27A0798700E00531 /* LottieLogger.swift in Sources */,
2E9C976F2822F43100677516 /* KeyframeExtensions.swift in Sources */,
Expand Down Expand Up @@ -2342,6 +2363,7 @@
2E9C97042822F43100677516 /* PreCompLayer.swift in Sources */,
2E9C96EC2822F43100677516 /* SolidLayer.swift in Sources */,
2EAF5AA327A0798700E00531 /* AnimationSubview.macOS.swift in Sources */,
57210915291073E400169699 /* RoundedCorners.swift in Sources */,
2E9C96C82822F43100677516 /* GroupInterpolator.swift in Sources */,
2E9C96F22822F43100677516 /* TransformLayer.swift in Sources */,
2E9C96352822F43100677516 /* Font.swift in Sources */,
Expand All @@ -2356,6 +2378,7 @@
2E9C974C2822F43100677516 /* CGColor+RGB.swift in Sources */,
2E9C96592822F43100677516 /* ShapeCompositionLayer.swift in Sources */,
2E9C96F52822F43100677516 /* AnimationLayer.swift in Sources */,
5721091D2910874A00169699 /* RoundedCornersNode.swift in Sources */,
2E9C95FC2822F43100677516 /* Star.swift in Sources */,
2E9C96202822F43100677516 /* KeyedDecodingContainerExtensions.swift in Sources */,
2E9C96532822F43100677516 /* PreCompositionLayer.swift in Sources */,
Expand Down Expand Up @@ -2433,7 +2456,8 @@
2E9C96AA2822F43100677516 /* FillNode.swift in Sources */,
2EAF5ACD27A0798700E00531 /* LottieAnimationViewBase.swift in Sources */,
2E9C96CE2822F43100677516 /* ShapeRenderLayer.swift in Sources */,
6C48780428FF20140005AF07 /* DotLottieFile.swift in Sources */,
5721092129119F3100169699 /* BezierPathRoundExtension.swift in Sources */,
6C48780428FF20140005AF07 /* DotLottieFile.swift in Sources */
2EAF5AEE27A0798700E00531 /* LottieLogger.swift in Sources */,
2E9C97702822F43100677516 /* KeyframeExtensions.swift in Sources */,
2E9C963B2822F43100677516 /* PrecompAsset.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ extension Array where Element == ShapeItem {
let node = TrimPathNode(parentNode: nodeTree.rootNode, trim: trim, upstreamPaths: nodeTree.paths)
nodeTree.rootNode = node
nodeTree.childrenNodes.append(node)
} else if let roundedCorners = item as? RoundedCorners {
let node = RoundedCornersNode(
parentNode: nodeTree.rootNode,
roundedCorners: roundedCorners,
upstreamPaths: nodeTree.paths)
nodeTree.rootNode = node
nodeTree.childrenNodes.append(node)
} else if let xform = item as? ShapeTransform {
nodeTree.transform = xform
continue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// RoundedCornersNode.swift
// Lottie
//
// Created by Duolingo on 10/31/22.
Copy link
Member

Choose a reason for hiding this comment

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

You work at Duolingo? Cool!

I have a 1083 day streak learning Spanish 😄 Really love what Duolingo has been doing with Lottie lately -- cutting edge stuff. The new feature where the characters lip-sync with the text-to-speech synth honestly blew my mind. Is that using Lottie or some sort of custom lip sync renderer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah! Glad you're enjoying that! I implemented that on iOS actually :D. We couldn't figure out a way to do that with Lottie so we ended up having to use a different more state-machine based animation Library

Copy link
Member

Choose a reason for hiding this comment

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

So cool, awesome work! (Not surprised that isn't possible with Lottie)

Are you on Twitter? Would love to give you a follow if so -- I'm https://twitter.com/calstephens98

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't use it much for work-related stuff but gave you a follow!

//

import Foundation
import QuartzCore

// MARK: - RoundedCornersProperties

final class RoundedCornersProperties: NodePropertyMap, KeypathSearchable {

// MARK: Lifecycle

init(roundedCorners: RoundedCorners) {
keypathName = roundedCorners.name
radius = NodeProperty(provider: KeyframeInterpolator(keyframes: roundedCorners.radius.keyframes))
keypathProperties = ["Radius" : radius]
properties = Array(keypathProperties.values)
}

// MARK: Internal

let keypathProperties: [String: AnyNodeProperty]
let properties: [AnyNodeProperty]
let keypathName: String

let radius: NodeProperty<LottieVector1D>
}

// MARK: - RoundedCornersNode

final class RoundedCornersNode: AnimatorNode {

// MARK: Lifecycle

init(parentNode: AnimatorNode?, roundedCorners: RoundedCorners, upstreamPaths: [PathOutputNode]) {
outputNode = PassThroughOutputNode(parent: parentNode?.outputNode)
self.parentNode = parentNode
properties = RoundedCornersProperties(roundedCorners: roundedCorners)
self.upstreamPaths = upstreamPaths
}

// MARK: Internal

let properties: RoundedCornersProperties

let parentNode: AnimatorNode?
let outputNode: NodeOutput
var hasLocalUpdates = false
var hasUpstreamUpdates = false
var lastUpdateFrame: CGFloat? = nil
var isEnabled = true

// MARK: Animator Node
var propertyMap: NodePropertyMap & KeypathSearchable {
properties
}

func forceUpstreamOutputUpdates() -> Bool {
hasLocalUpdates || hasUpstreamUpdates
}

func rebuildOutputs(frame: CGFloat) {
for pathContainer in upstreamPaths {
let pathObjects = pathContainer.removePaths(updateFrame: frame)
for path in pathObjects {
pathContainer.appendPath(
path.roundCorners(
radius: properties.radius.value.cgFloatValue),
updateFrame: frame)
}
}
}

// MARK: Fileprivate

fileprivate let upstreamPaths: [PathOutputNode]
}
47 changes: 47 additions & 0 deletions Sources/Private/Model/ShapeItems/RoundedCorners.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// RoundedCorners.swift
// Lottie
//
// Created by Duolingo on 10/31/22.
//

import Foundation

// MARK: - RoundedCorners

final class RoundedCorners: ShapeItem {

// MARK: Lifecycle

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoundedCorners.CodingKeys.self)
radius = try
container.decode(
KeyframeGroup<LottieVector1D>.self,
forKey: .radius)
try super.init(from: decoder)
}

required init(dictionary: [String: Any]) throws {
let radiusDictionary: [String: Any] = try dictionary.value(for: CodingKeys.radius)
radius = try KeyframeGroup<LottieVector1D>(dictionary: radiusDictionary)
try super.init(dictionary: dictionary)
}

// MARK: Internal

/// The radius of rounded corners
let radius: KeyframeGroup<LottieVector1D>

override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(radius, forKey: .radius)
}

// MARK: Private

private enum CodingKeys: String, CodingKey {
case radius = "r"
}
}
4 changes: 4 additions & 0 deletions Sources/Private/Model/ShapeItems/ShapeItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extension ShapeType: ClassFamily {
return Rectangle.self
case .repeater:
return Repeater.self
case .round:
return RoundedCorners.self
case .shape:
return Shape.self
case .star:
Expand Down Expand Up @@ -143,6 +145,8 @@ extension Array where Element == ShapeItem {
return try Rectangle(dictionary: dictionary)
case .repeater:
return try Repeater(dictionary: dictionary)
case .round:
return try RoundedCorners(dictionary: dictionary)
case .shape:
return try Shape(dictionary: dictionary)
case .star:
Expand Down
Loading