Wallpaper_Home/wallpaper_project/Pods/lottie-ios/Sources/Public/Controls/AnimatedControl.swift
2024-04-17 14:37:20 +08:00

246 lines
6.5 KiB
Swift

//
// AnimatedControl.swift
// lottie-swift
//
// Created by Brandon Withrow on 2/4/19.
//
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// MARK: - AnimatedControl
/// Lottie comes prepacked with a two Animated Controls, `AnimatedSwitch` and
/// `AnimatedButton`. Both of these controls are built on top of `AnimatedControl`
///
/// `AnimatedControl` is a subclass of `UIControl` that provides an interactive
/// mechanism for controlling the visual state of an animation in response to
/// user actions.
///
/// The `AnimatedControl` will show and hide layers depending on the current
/// `UIControl.State` of the control.
///
/// Users of `AnimationControl` can set a Layer Name for each `UIControl.State`.
/// When the state is change the `AnimationControl` will change the visibility
/// of its layers.
///
/// NOTE: Do not initialize directly. This is intended to be subclassed.
open class AnimatedControl: LottieControlType {
// MARK: Lifecycle
// MARK: Initializers
public init(
animation: LottieAnimation?,
configuration: LottieConfiguration = .shared)
{
animationView = LottieAnimationView(
animation: animation,
configuration: configuration)
super.init(frame: animation?.bounds ?? .zero)
commonInit()
}
public init() {
animationView = LottieAnimationView()
super.init(frame: .zero)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
animationView = LottieAnimationView()
super.init(coder: aDecoder)
commonInit()
}
// MARK: Open
// MARK: UIControl Overrides
open override var isEnabled: Bool {
didSet {
updateForState()
}
}
#if canImport(UIKit)
open override var isSelected: Bool {
didSet {
updateForState()
}
}
#endif
open override var isHighlighted: Bool {
didSet {
updateForState()
}
}
open override var intrinsicContentSize: CGSize {
animationView.intrinsicContentSize
}
#if canImport(UIKit)
open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
updateForState()
return super.beginTracking(touch, with: event)
}
open override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
updateForState()
return super.continueTracking(touch, with: event)
}
open override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
updateForState()
return super.endTracking(touch, with: event)
}
open override func cancelTracking(with event: UIEvent?) {
updateForState()
super.cancelTracking(with: event)
}
#elseif canImport(AppKit)
open override func mouseDown(with mouseDownEvent: NSEvent) {
guard let window else { return }
currentState = .highlighted
updateForState()
handle(LottieControlEvent(mouseDownEvent.type, inside: eventIsInside(mouseDownEvent)))
// AppKit mouse-tracking loop from:
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingMouseEvents/HandlingMouseEvents.html#//apple_ref/doc/uid/10000060i-CH6-SW4
var keepOn = true
while
keepOn,
let event = window.nextEvent(
matching: .any,
until: .distantFuture,
inMode: .eventTracking,
dequeue: true)
{
if event.type == .leftMouseUp {
keepOn = false
}
let isInside = eventIsInside(event)
handle(LottieControlEvent(event.type, inside: isInside))
let expectedState = (isInside && keepOn) ? LottieNSControlState.highlighted : .normal
if currentState != expectedState {
currentState = expectedState
updateForState()
}
}
}
func handle(_: LottieNSControlEvent) {
// To be overridden in subclasses
}
private func eventIsInside(_ event: NSEvent) -> Bool {
let mouseLocation = convert(event.locationInWindow, from: nil)
return isMousePoint(mouseLocation, in: bounds)
}
#endif
open func animationDidSet() { }
// MARK: Public
/// The animation view in which the animation is rendered.
public let animationView: LottieAnimationView
/// The animation backing the animated control.
public var animation: LottieAnimation? {
didSet {
animationView.animation = animation
animationView.bounds = animation?.bounds ?? .zero
#if canImport(UIKit)
setNeedsLayout()
#elseif canImport(AppKit)
needsLayout = true
#endif
updateForState()
animationDidSet()
}
}
/// The speed of the animation playback. Defaults to 1
public var animationSpeed: CGFloat {
set { animationView.animationSpeed = newValue }
get { animationView.animationSpeed }
}
/// Sets which Animation Layer should be visible for the given state.
public func setLayer(named: String, forState: LottieControlState) {
stateMap[forState.rawValue] = named
updateForState()
}
/// Sets a ValueProvider for the specified keypath
public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) {
animationView.setValueProvider(valueProvider, keypath: keypath)
}
// MARK: Internal
var stateMap: [UInt: String] = [:]
#if canImport(UIKit)
var currentState: LottieControlState {
state
}
#elseif canImport(AppKit)
var currentState = LottieControlState.normal
#endif
func updateForState() {
guard let animationLayer = animationView.animationLayer else { return }
if
let layerName = stateMap[currentState.rawValue],
let stateLayer = animationLayer.layer(for: AnimationKeypath(keypath: layerName))
{
for layer in animationLayer._animationLayers {
layer.isHidden = true
}
stateLayer.isHidden = false
} else {
for layer in animationLayer._animationLayers {
layer.isHidden = false
}
}
}
// MARK: Private
private func commonInit() {
#if canImport(UIKit)
animationView.clipsToBounds = false
clipsToBounds = true
#endif
animationView.translatesAutoresizingMaskIntoConstraints = false
animationView.backgroundBehavior = .forceFinish
addSubview(animationView)
animationView.contentMode = .scaleAspectFit
#if canImport(UIKit)
animationView.isUserInteractionEnabled = false
#endif
animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true
animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}