LottiePlaybackMode.swift 10.9 KB
Newer Older
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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
// Created by Cal Stephens on 8/3/23.
// Copyright © 2023 Airbnb Inc. All rights reserved.

import Foundation

// MARK: - LottiePlaybackMode

/// Configuration for how a Lottie animation should be played
public enum LottiePlaybackMode: Hashable {

  /// The animation is paused at the given state (e.g. paused at a specific frame)
  case paused(at: PausedState)

  /// The animation is playing using the given playback mode (e.g. looping from the start to the end)
  case playing(_ mode: PlaybackMode)

  @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.")
  case progress(_ progress: AnimationProgressTime)

  @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.")
  case frame(_ frame: AnimationFrameTime)

  @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.")
  case time(_ time: TimeInterval)

  @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.")
  case pause

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  case fromProgress(_ fromProgress: AnimationProgressTime?, toProgress: AnimationProgressTime, loopMode: LottieLoopMode)

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  case fromFrame(_ fromFrame: AnimationFrameTime?, toFrame: AnimationFrameTime, loopMode: LottieLoopMode)

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  case fromMarker(
    _ fromMarker: String?,
    toMarker: String,
    playEndMarkerFrame: Bool = true,
    loopMode: LottieLoopMode)

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  case marker(_ marker: String, loopMode: LottieLoopMode)

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  case markers(_ markers: [String])

  // MARK: Public

  public enum PausedState: Hashable {
    /// Any existing animation will be paused at the current frame.
    case currentFrame

    /// The animation is paused at the given progress value,
    /// a value between 0.0 (0% progress) and 1.0 (100% progress).
    case progress(_ progress: AnimationProgressTime)

    /// The animation is paused at the given frame of the animation.
    case frame(_ frame: AnimationFrameTime)

    /// The animation is paused at the given time value from the start of the animation.
    case time(_ time: TimeInterval)

    /// Pauses the animation at a given marker and position
    case marker(_ name: String, position: LottieMarkerPosition = .start)
  }

  public enum PlaybackMode: Hashable {
    /// Plays the animation from a progress (0-1) to a progress (0-1).
    /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress.
    /// - Parameter toProgress: The end progress of the animation.
    /// - Parameter loopMode: The loop behavior of the animation.
    case fromProgress(
      _ fromProgress: AnimationProgressTime?,
      toProgress: AnimationProgressTime,
      loopMode: LottieLoopMode)

    /// The animation plays from the given `fromFrame` to the given `toFrame`.
    /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame.
    /// - Parameter toFrame: The end frame of the animation.
    /// - Parameter loopMode: The loop behavior of the animation.
    case fromFrame(
      _ fromFrame: AnimationFrameTime?,
      toFrame: AnimationFrameTime,
      loopMode: LottieLoopMode)

    /// Plays the animation from a named marker to another marker.
    ///
    /// Markers are point in time that are encoded into the Animation data and assigned a name.
    ///
    /// NOTE: If markers are not found the play command will exit.
    ///
    /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the
    /// animation will start at the current progress.
    /// - Parameter toMarker: The end marker for the animation playback.
    /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
    /// end marker represents the end of the section to play, it should be to true. If the provided end marker
    /// represents the beginning of the next section, it should be false.
    /// - Parameter loopMode: The loop behavior of the animation.
    case fromMarker(
      _ fromMarker: String?,
      toMarker: String,
      playEndMarkerFrame: Bool = true,
      loopMode: LottieLoopMode)

    /// Plays the animation from a named marker to the end of the marker's duration.
    ///
    /// A marker is a point in time with an associated duration that is encoded into the
    /// animation data and assigned a name.
    ///
    /// NOTE: If marker is not found the play command will exit.
    ///
    /// - Parameter marker: The start marker for the animation playback.
    /// - Parameter loopMode: The loop behavior of the animation.
    case marker(
      _ marker: String,
      loopMode: LottieLoopMode)

    /// Plays the given markers sequentially in order.
    ///
    /// A marker is a point in time with an associated duration that is encoded into the
    /// animation data and assigned a name. Multiple markers can be played sequentially
    /// to create programmable animations.
    ///
    /// If a marker is not found, it will be skipped.
    ///
    /// If a marker doesn't have a duration value, it will play with a duration of 0
    /// (effectively being skipped).
    ///
    /// If another animation is played (by calling any `play` method) while this
    /// marker sequence is playing, the marker sequence will be cancelled.
    ///
    /// - Parameter markers: The list of markers to play sequentially.
    case markers(_ markers: [String])
  }

}

extension LottiePlaybackMode {
  public static var paused: Self {
    .paused(at: .currentFrame)
  }

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  public static func toProgress(_ toProgress: AnimationProgressTime, loopMode: LottieLoopMode) -> LottiePlaybackMode {
    .playing(.fromProgress(nil, toProgress: toProgress, loopMode: loopMode))
  }

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  public static func toFrame(_ toFrame: AnimationFrameTime, loopMode: LottieLoopMode) -> LottiePlaybackMode {
    .playing(.fromFrame(nil, toFrame: toFrame, loopMode: loopMode))
  }

  @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.")
  public static func toMarker(
    _ toMarker: String,
    playEndMarkerFrame: Bool = true,
    loopMode: LottieLoopMode)
    -> LottiePlaybackMode
  {
    .playing(.fromMarker(nil, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: loopMode))
  }
}

extension LottiePlaybackMode.PlaybackMode {
  /// Plays the animation from the current progress to a progress value (0-1).
  /// - Parameter toProgress: The end progress of the animation.
  /// - Parameter loopMode: The loop behavior of the animation.
  public static func toProgress(_ toProgress: AnimationProgressTime, loopMode: LottieLoopMode) -> Self {
    .fromProgress(nil, toProgress: toProgress, loopMode: loopMode)
  }

  /// Plays the animation from the current frame to the given frame.
  /// - Parameter toFrame: The end frame of the animation.
  /// - Parameter loopMode: The loop behavior of the animation.
  public static func toFrame(_ toFrame: AnimationFrameTime, loopMode: LottieLoopMode) -> Self {
    .fromFrame(nil, toFrame: toFrame, loopMode: loopMode)
  }

  /// Plays the animation from the current frame to some marker.
  ///
  /// Markers are point in time that are encoded into the Animation data and assigned a name.
  ///
  /// NOTE: If the marker isn't found the play command will exit.
  ///
  /// - Parameter toMarker: The end marker for the animation playback.
  /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
  /// end marker represents the end of the section to play, it should be to true. If the provided end marker
  /// represents the beginning of the next section, it should be false.
  /// - Parameter loopMode: The loop behavior of the animation.
  public static func toMarker(
    _ toMarker: String,
    playEndMarkerFrame: Bool = true,
    loopMode: LottieLoopMode)
    -> Self
  {
    .fromMarker(nil, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: loopMode)
  }
}

// MARK: - LottieMarkerPosition

/// The position within a marker.
public enum LottieMarkerPosition: Hashable {
  case start
  case end
}

extension LottiePlaybackMode {
  /// Returns a copy of this `PlaybackMode` with the `LottieLoopMode` updated to the given value
  func loopMode(_ updatedLoopMode: LottieLoopMode) -> LottiePlaybackMode {
    switch self {
    case .playing(let playbackMode):
      .playing(playbackMode.loopMode(updatedLoopMode))

    case .fromProgress(let fromProgress, toProgress: let toProgress, _):
      .playing(.fromProgress(
        fromProgress,
        toProgress: toProgress,
        loopMode: updatedLoopMode))

    case .fromFrame(let fromFrame, toFrame: let toFrame, _):
      .playing(.fromFrame(
        fromFrame,
        toFrame: toFrame,
        loopMode: updatedLoopMode))

    case .fromMarker(let fromMarker, let toMarker, let playEndMarkerFrame, _):
      .playing(.fromMarker(
        fromMarker,
        toMarker: toMarker,
        playEndMarkerFrame: playEndMarkerFrame,
        loopMode: updatedLoopMode))

    case .marker(let marker, _):
      .playing(.marker(marker, loopMode: updatedLoopMode))

    case .pause, .paused, .progress(_), .time(_), .frame(_), .markers:
      self
    }
  }
}

extension LottiePlaybackMode.PlaybackMode {
  /// Returns a copy of this `PlaybackMode` with the `LottieLoopMode` updated to the given value
  func loopMode(_ updatedLoopMode: LottieLoopMode) -> LottiePlaybackMode.PlaybackMode {
    switch self {
    case .fromProgress(let fromProgress, let toProgress, _):
      .fromProgress(fromProgress, toProgress: toProgress, loopMode: updatedLoopMode)
    case .fromFrame(let fromFrame, let toFrame, _):
      .fromFrame(fromFrame, toFrame: toFrame, loopMode: updatedLoopMode)
    case .fromMarker(let fromMarker, let toMarker, let playEndMarkerFrame, _):
      .fromMarker(fromMarker, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: updatedLoopMode)
    case .marker(let marker, _):
      .marker(marker, loopMode: updatedLoopMode)
    case .markers:
      self
    }
  }
}