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

Added: PathValueProvider #2376

Closed
wants to merge 11 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ extension CurveVertex: Interpolatable {
// MARK: - BezierPath + Interpolatable

extension BezierPath: Interpolatable {
func interpolate(to: BezierPath, amount: CGFloat) -> BezierPath {
public func interpolate(to: BezierPath, amount: CGFloat) -> BezierPath {
var newPath = BezierPath()
for i in 0..<min(elements.count, to.elements.count) {
let fromVertex = elements[i].vertex
Expand Down
41 changes: 22 additions & 19 deletions Sources/Private/Utility/Primitives/BezierPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import CoreGraphics
// MARK: - BezierPath

/// A container that holds instructions for creating a single, unbroken Bezier Path.
struct BezierPath {
public struct BezierPath {
calda marked this conversation as resolved.
Show resolved Hide resolved

// MARK: Lifecycle

Expand Down Expand Up @@ -289,7 +289,7 @@ extension BezierPath: Codable {

// MARK: Lifecycle

init(from decoder: Decoder) throws {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<BezierPath.CodingKeys>

if let keyedContainer = try? decoder.container(keyedBy: BezierPath.CodingKeys.self) {
Expand Down Expand Up @@ -351,6 +351,26 @@ extension BezierPath: Codable {
elements = decodedElements
}

// MARK: Public

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: BezierPath.CodingKeys.self)
try container.encode(closed, forKey: .closed)

var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices)
var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints)
var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints)

/// If closed path, ignore the final element.
let finalIndex = closed ? elements.endIndex - 1 : elements.endIndex
for i in 0..<finalIndex {
let element = elements[i]
try vertexContainer.encode(element.vertex.point)
try inPointsContainer.encode(element.vertex.inTangentRelative)
try outPointsContainer.encode(element.vertex.outTangentRelative)
}
}

// MARK: Internal

/// The BezierPath container is encoded and decoded from the JSON format
Expand All @@ -371,23 +391,6 @@ extension BezierPath: Codable {
case vertices = "v"
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: BezierPath.CodingKeys.self)
try container.encode(closed, forKey: .closed)

var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices)
var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints)
var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints)

/// If closed path, ignore the final element.
let finalIndex = closed ? elements.endIndex - 1 : elements.endIndex
for i in 0..<finalIndex {
let element = elements[i]
try vertexContainer.encode(element.vertex.point)
try inPointsContainer.encode(element.vertex.inTangentRelative)
try outPointsContainer.encode(element.vertex.outTangentRelative)
}
}
}

// MARK: AnyInitializable
Expand Down
47 changes: 47 additions & 0 deletions Sources/Private/Utility/Primitives/CGPathExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// CGPathExtension.swift
// Lottie
//
// Created by Yuval Kalugny on 13/04/2024.
//

import CoreGraphics

extension CGPath {

var bezierPath: BezierPath {
var path = BezierPath()

applyWithBlock { element in
switch element.pointee.type {
case .moveToPoint:
let point = element.pointee.points[0]
path.moveToStartPoint(.init(.zero, point, .zero))
case .addLineToPoint:
let point = element.pointee.points[0]
path.addLine(toPoint: point)
case .addQuadCurveToPoint:
let controlPoint = element.pointee.points[0]
let endPoint = element.pointee.points[1]
let controlPoint1 = CGPoint(
x: (2.0 / 3.0) * (controlPoint.x - element.pointee.points[-1].x) + element.pointee.points[-1].x,
y: (2.0 / 3.0) * (controlPoint.y - element.pointee.points[-1].y) + element.pointee.points[-1].y)
let controlPoint2 = CGPoint(
x: (2.0 / 3.0) * (controlPoint.x - endPoint.x) + endPoint.x,
y: (2.0 / 3.0) * (controlPoint.y - endPoint.y) + endPoint.y)
path.addCurve(toPoint: endPoint, outTangent: controlPoint1, inTangent: controlPoint2)
case .addCurveToPoint:
let controlPoint1 = element.pointee.points[0]
let controlPoint2 = element.pointee.points[1]
let endPoint = element.pointee.points[2]
path.addCurve(toPoint: endPoint, outTangent: controlPoint1, inTangent: controlPoint2)
case .closeSubpath:
path.close()
@unknown default:
break
}
}

return path
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// PathValueProvider.swift
// lottie-swift
//
// Created by Yuval Kalugny on 13/04/2024
//

import CoreGraphics
import Foundation

// MARK: - PathValueProvider

/// A `ValueProvider` that returns a CGPath Value
public final class PathValueProvider: ValueProvider {

// MARK: Lifecycle

/// Initializes with a block provider
public init(block: @escaping PathValueBlock) {
self.block = block
path = .init(rect: .zero, transform: nil)
identity = UUID()
}

/// Initializes with a single path.
public init(_ path: CGPath) {
self.path = path
block = nil
hasUpdate = true
identity = path.hashValue
}

// MARK: Public

/// Returns a CGPath for a CGFloat(Frame Time)
public typealias PathValueBlock = (CGFloat) -> CGPath

public var path: CGPath {
didSet {
hasUpdate = true
}
}

// MARK: ValueProvider Protocol

public var valueType: Any.Type {
BezierPath.self
}

public var storage: ValueProviderStorage<BezierPath> {
if let block {
return .closure { frame in
self.hasUpdate = false
return block(frame).bezierPath
}
} else {
hasUpdate = false
return .singleValue(path.bezierPath)
}
}

public func hasUpdate(frame _: CGFloat) -> Bool {
if block != nil {
return true
}
return hasUpdate
}

// MARK: Private

private var hasUpdate = true

private var block: PathValueBlock?
private let identity: AnyHashable
}

// MARK: Equatable

extension PathValueProvider: Equatable {
public static func ==(_ lhs: PathValueProvider, _ rhs: PathValueProvider) -> Bool {
lhs.identity == rhs.identity
}
}