📦 airbnb / lottie-ios

📄 GradientValueProvider.swift · 139 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139//
//  GradientValueProvider.swift
//  lottie-swift
//
//  Created by Enrique Bermúdez on 10/27/19.
//

import CoreGraphics
import Foundation

// MARK: - GradientValueProvider

/// A `ValueProvider` that returns a Gradient Color Value.
public final class GradientValueProvider: ValueProvider {

  // MARK: Lifecycle

  /// Initializes with a block provider.
  public init(
    block: @escaping ColorsValueBlock,
    locations: ColorLocationsBlock? = nil
  ) {
    self.block = block
    locationsBlock = locations
    colors = []
    self.locations = []
    identity = UUID()
  }

  /// Initializes with an array of colors.
  public init(
    _ colors: [LottieColor],
    locations: [Double] = []
  ) {
    self.colors = colors
    self.locations = locations
    identity = [AnyHashable(colors), AnyHashable(locations)]
    updateValueArray()
    hasUpdate = true
  }

  // MARK: Public

  /// Returns a [LottieColor] for a CGFloat(Frame Time).
  public typealias ColorsValueBlock = (CGFloat) -> [LottieColor]
  /// Returns a [Double](Color locations) for a CGFloat(Frame Time).
  public typealias ColorLocationsBlock = (CGFloat) -> [Double]

  /// The colors values of the provider.
  public var colors: [LottieColor] {
    didSet {
      updateValueArray()
      hasUpdate = true
    }
  }

  /// The color location values of the provider.
  public var locations: [Double] {
    didSet {
      updateValueArray()
      hasUpdate = true
    }
  }

  public var valueType: Any.Type {
    [Double].self
  }

  public var storage: ValueProviderStorage<[Double]> {
    if let block {
      .closure { [self] frame in
        hasUpdate = false

        let newColors = block(frame)
        let newLocations = locationsBlock?(frame) ?? []
        value = value(from: newColors, locations: newLocations)

        return value
      }
    } else {
      .singleValue(value)
    }
  }

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

  // MARK: Private

  private var hasUpdate = true

  private var block: ColorsValueBlock?
  private var locationsBlock: ColorLocationsBlock?
  private var value = [Double]()

  private let identity: AnyHashable

  private func value(from colors: [LottieColor], locations: [Double]) -> [Double] {
    var colorValues = [Double]()
    var alphaValues = [Double]()
    var shouldAddAlphaValues = false

    for i in 0..<colors.count {
      if colors[i].a < 1 { shouldAddAlphaValues = true }

      let location = locations.count > i
        ? locations[i]
        : (Double(i) / Double(colors.count - 1))

      colorValues.append(location)
      colorValues.append(colors[i].r)
      colorValues.append(colors[i].g)
      colorValues.append(colors[i].b)

      alphaValues.append(location)
      alphaValues.append(colors[i].a)
    }

    return colorValues + (shouldAddAlphaValues ? alphaValues : [])
  }

  private func updateValueArray() {
    value = value(from: colors, locations: locations)
  }

}

// MARK: Equatable

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