190 lines
4.6 KiB
Swift
190 lines
4.6 KiB
Swift
//
|
|
// MaskContainerLayer.swift
|
|
// lottie-swift
|
|
//
|
|
// Created by Brandon Withrow on 1/25/19.
|
|
//
|
|
|
|
import Foundation
|
|
import QuartzCore
|
|
|
|
extension MaskMode {
|
|
var usableMode: MaskMode {
|
|
switch self {
|
|
case .add:
|
|
return .add
|
|
case .subtract:
|
|
return .subtract
|
|
case .intersect:
|
|
return .intersect
|
|
case .lighten:
|
|
return .add
|
|
case .darken:
|
|
return .darken
|
|
case .difference:
|
|
return .intersect
|
|
case .none:
|
|
return .none
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - MaskContainerLayer
|
|
|
|
final class MaskContainerLayer: CALayer {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(masks: [Mask]) {
|
|
super.init()
|
|
anchorPoint = .zero
|
|
var containerLayer = CALayer()
|
|
var firstObject = true
|
|
for mask in masks {
|
|
let maskLayer = MaskLayer(mask: mask)
|
|
maskLayers.append(maskLayer)
|
|
if mask.mode.usableMode == .none {
|
|
continue
|
|
} else if mask.mode.usableMode == .add || firstObject {
|
|
firstObject = false
|
|
containerLayer.addSublayer(maskLayer)
|
|
} else {
|
|
containerLayer.mask = maskLayer
|
|
let newContainer = CALayer()
|
|
newContainer.addSublayer(containerLayer)
|
|
containerLayer = newContainer
|
|
}
|
|
}
|
|
addSublayer(containerLayer)
|
|
}
|
|
|
|
override init(layer: Any) {
|
|
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
|
|
guard let layer = layer as? MaskContainerLayer else {
|
|
fatalError("init(layer:) Wrong Layer Class")
|
|
}
|
|
super.init(layer: layer)
|
|
}
|
|
|
|
required init?(coder _: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
func updateWithFrame(frame: CGFloat, forceUpdates: Bool) {
|
|
maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) })
|
|
}
|
|
|
|
// MARK: Fileprivate
|
|
|
|
fileprivate var maskLayers: [MaskLayer] = []
|
|
}
|
|
|
|
extension CGRect {
|
|
static var veryLargeRect: CGRect {
|
|
CGRect(
|
|
x: -100_000_000,
|
|
y: -100_000_000,
|
|
width: 200_000_000,
|
|
height: 200_000_000)
|
|
}
|
|
}
|
|
|
|
// MARK: - MaskLayer
|
|
|
|
private class MaskLayer: CALayer {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(mask: Mask) {
|
|
properties = MaskNodeProperties(mask: mask)
|
|
super.init()
|
|
addSublayer(maskLayer)
|
|
anchorPoint = .zero
|
|
maskLayer.fillColor = mask.mode == .add
|
|
? CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1])
|
|
: CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1])
|
|
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
|
|
actions = [
|
|
"opacity" : NSNull(),
|
|
]
|
|
}
|
|
|
|
override init(layer: Any) {
|
|
properties = nil
|
|
super.init(layer: layer)
|
|
}
|
|
|
|
required init?(coder _: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
let properties: MaskNodeProperties?
|
|
|
|
let maskLayer = CAShapeLayer()
|
|
|
|
func updateWithFrame(frame: CGFloat, forceUpdates: Bool) {
|
|
guard let properties = properties else { return }
|
|
if properties.opacity.needsUpdate(frame: frame) || forceUpdates {
|
|
properties.opacity.update(frame: frame)
|
|
opacity = Float(properties.opacity.value.cgFloatValue)
|
|
}
|
|
|
|
if properties.shape.needsUpdate(frame: frame) || forceUpdates {
|
|
properties.shape.update(frame: frame)
|
|
properties.expansion.update(frame: frame)
|
|
|
|
let shapePath = properties.shape.value.cgPath()
|
|
var path = shapePath
|
|
if
|
|
properties.mode.usableMode == .subtract && !properties.inverted ||
|
|
(properties.mode.usableMode == .add && properties.inverted)
|
|
{
|
|
/// Add a bounds rect to invert the mask
|
|
let newPath = CGMutablePath()
|
|
newPath.addRect(CGRect.veryLargeRect)
|
|
newPath.addPath(shapePath)
|
|
path = newPath
|
|
}
|
|
maskLayer.path = path
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - MaskNodeProperties
|
|
|
|
private class MaskNodeProperties: NodePropertyMap {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(mask: Mask) {
|
|
mode = mask.mode
|
|
inverted = mask.inverted
|
|
opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes))
|
|
shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes))
|
|
expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes))
|
|
propertyMap = [
|
|
"Opacity" : opacity,
|
|
"Shape" : shape,
|
|
"Expansion" : expansion,
|
|
]
|
|
properties = Array(propertyMap.values)
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
var propertyMap: [String: AnyNodeProperty]
|
|
|
|
var properties: [AnyNodeProperty]
|
|
|
|
let mode: MaskMode
|
|
let inverted: Bool
|
|
|
|
let opacity: NodeProperty<Vector1D>
|
|
let shape: NodeProperty<BezierPath>
|
|
let expansion: NodeProperty<Vector1D>
|
|
}
|