Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,39 @@
//
// KeyframeGroupInterpolator.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/22/19.
//
import CoreGraphics
import Foundation
/// A value provider that produces an array of values from an array of Keyframe Interpolators
final class GroupInterpolator<ValueType>: ValueProvider where ValueType: Interpolatable {
// MARK: Lifecycle
/// Initialize with an array of array of keyframes.
init(keyframeGroups: ContiguousArray<ContiguousArray<Keyframe<ValueType>>>) {
keyframeInterpolators = ContiguousArray(keyframeGroups.map({ KeyframeInterpolator(keyframes: $0) }))
}
// MARK: Internal
let keyframeInterpolators: ContiguousArray<KeyframeInterpolator<ValueType>>
var valueType: Any.Type {
[ValueType].self
}
var storage: ValueProviderStorage<[ValueType]> {
.closure { frame in
self.keyframeInterpolators.map({ $0.value(frame: frame) as! ValueType })
}
}
func hasUpdate(frame: CGFloat) -> Bool {
let updated = keyframeInterpolators.first(where: { $0.hasUpdate(frame: frame) })
return updated != nil
}
}
@@ -0,0 +1,253 @@
//
// KeyframeInterpolator.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/15/19.
//
import CoreGraphics
import Foundation
// MARK: - KeyframeInterpolator
/// A value provider that produces a value at Time from a group of keyframes
final class KeyframeInterpolator<ValueType>: ValueProvider where ValueType: AnyInterpolatable {
// MARK: Lifecycle
init(keyframes: ContiguousArray<Keyframe<ValueType>>) {
self.keyframes = keyframes
}
// MARK: Internal
let keyframes: ContiguousArray<Keyframe<ValueType>>
var valueType: Any.Type {
ValueType.self
}
var storage: ValueProviderStorage<ValueType> {
.closure { [self] frame in
// First set the keyframe span for the frame.
updateSpanIndices(frame: frame)
lastUpdatedFrame = frame
// If only one keyframe return its value
let progress: CGFloat
let value: ValueType
if
let leading = leadingKeyframe,
let trailing = trailingKeyframe
{
/// We have leading and trailing keyframe.
progress = leading.interpolatedProgress(trailing, keyTime: frame)
value = leading.interpolate(to: trailing, progress: progress)
} else if let leading = leadingKeyframe {
progress = 0
value = leading.value
} else if let trailing = trailingKeyframe {
progress = 1
value = trailing.value
} else {
/// Satisfy the compiler.
progress = 0
value = keyframes[0].value
}
return value
}
}
/// Returns true to trigger a frame update for this interpolator.
///
/// An interpolator will be asked if it needs to update every frame.
/// If the interpolator needs updating it will be asked to compute its value for
/// the given frame.
///
/// Cases a keyframe should not be updated:
/// - If time is in span and leading keyframe is hold
/// - If time is after the last keyframe.
/// - If time is before the first keyframe
///
/// Cases for updating a keyframe:
/// - If time is in the span, and is not a hold
/// - If time is outside of the span, and there are more keyframes
/// - If a value delegate is set
/// - If leading and trailing are both nil.
func hasUpdate(frame: CGFloat) -> Bool {
if lastUpdatedFrame == nil {
return true
}
if
let leading = leadingKeyframe,
trailingKeyframe == nil,
leading.time < frame
{
/// Frame is after bounds of keyframes
return false
}
if
let trailing = trailingKeyframe,
leadingKeyframe == nil,
frame < trailing.time
{
/// Frame is before bounds of keyframes
return false
}
if
let leading = leadingKeyframe,
let trailing = trailingKeyframe,
leading.isHold,
leading.time < frame,
frame < trailing.time
{
return false
}
return true
}
// MARK: Fileprivate
fileprivate var lastUpdatedFrame: CGFloat?
fileprivate var leadingIndex: Int? = nil
fileprivate var trailingIndex: Int? = nil
fileprivate var leadingKeyframe: Keyframe<ValueType>? = nil
fileprivate var trailingKeyframe: Keyframe<ValueType>? = nil
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
fileprivate func updateSpanIndices(frame: CGFloat) {
guard keyframes.count > 0 else {
leadingIndex = nil
trailingIndex = nil
leadingKeyframe = nil
trailingKeyframe = nil
return
}
// This function searches through the array to find the span of two keyframes
// that contain the current time.
//
// We could use Array.first(where:) but that would search through the entire array
// each frame.
// Instead we track the last used index and search either forwards or
// backwards from there. This reduces the iterations and complexity from
//
// O(n), where n is the length of the sequence to
// O(n), where n is the number of items after or before the last used index.
//
if keyframes.count == 1 {
/// Only one keyframe. Set it as first and move on.
leadingIndex = 0
trailingIndex = nil
leadingKeyframe = keyframes[0]
trailingKeyframe = nil
return
}
/// Sets the initial keyframes. This is often only needed for the first check.
if
leadingIndex == nil &&
trailingIndex == nil
{
if frame < keyframes[0].time {
/// Time is before the first keyframe. Set it as the trailing.
trailingIndex = 0
} else {
/// Time is after the first keyframe. Set the keyframe and the trailing.
leadingIndex = 0
trailingIndex = 1
}
}
if
let currentTrailing = trailingIndex,
keyframes[currentTrailing].time <= frame
{
/// Time is after the current span. Iterate forward.
var newLeading = currentTrailing
var keyframeFound = false
while !keyframeFound {
leadingIndex = newLeading
trailingIndex = keyframes.validIndex(newLeading + 1)
guard let trailing = trailingIndex else {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true
continue
}
if frame < keyframes[trailing].time {
/// Keyframe in current span.
keyframeFound = true
continue
}
/// Advance the array.
newLeading = trailing
}
} else if
let currentLeading = leadingIndex,
frame < keyframes[currentLeading].time
{
/// Time is before the current span. Iterate backwards
var newTrailing = currentLeading
var keyframeFound = false
while !keyframeFound {
leadingIndex = keyframes.validIndex(newTrailing - 1)
trailingIndex = newTrailing
guard let leading = leadingIndex else {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true
continue
}
if keyframes[leading].time <= frame {
/// Keyframe in current span.
keyframeFound = true
continue
}
/// Step back
newTrailing = leading
}
}
if let keyFrame = leadingIndex {
leadingKeyframe = keyframes[keyFrame]
} else {
leadingKeyframe = nil
}
if let keyFrame = trailingIndex {
trailingKeyframe = keyframes[keyFrame]
} else {
trailingKeyframe = nil
}
}
}
extension Array {
fileprivate func validIndex(_ index: Int) -> Int? {
if 0 <= index, index < endIndex {
return index
}
return nil
}
}
extension ContiguousArray {
fileprivate func validIndex(_ index: Int) -> Int? {
if 0 <= index, index < endIndex {
return index
}
return nil
}
}
@@ -0,0 +1,43 @@
//
// SingleValueProvider.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/30/19.
//
import Foundation
import QuartzCore
/// Returns a value for every frame.
final class SingleValueProvider<ValueType: AnyInterpolatable>: ValueProvider {
// MARK: Lifecycle
init(_ value: ValueType) {
self.value = value
}
// MARK: Internal
var value: ValueType {
didSet {
hasUpdate = true
}
}
var storage: ValueProviderStorage<ValueType> {
.singleValue(value)
}
var valueType: Any.Type {
ValueType.self
}
func hasUpdate(frame _: CGFloat) -> Bool {
hasUpdate
}
// MARK: Private
private var hasUpdate = true
}