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 } } } extension CGRect { static var veryLargeRect: CGRect { return CGRect(x: -100_000_000, y: -100_000_000, width: 200_000_000, height: 200_000_000) } } final class MyMaskContainerLayer { init(masks: [Mask]) { //NOTE //anchorPoint = .zero var containerLayer = CALayer() var firstObject: Bool = 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 } } //NOTE //addSublayer(containerLayer) } fileprivate var maskLayers: [MaskLayer] = [] func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) }) } } fileprivate class MaskLayer: CALayer { let properties: MaskNodeProperties? let maskLayer = CAShapeLayer() init(mask: Mask) { self.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 self.actions = [ "opacity" : NSNull() ] } override init(layer: Any) { self.properties = nil super.init(layer: layer) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { guard let properties = properties else { return } if properties.opacity.needsUpdate(frame: frame) || forceUpdates { properties.opacity.update(frame: frame) self.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 } } } fileprivate class MaskNodeProperties: NodePropertyMap { var propertyMap: [String : AnyNodeProperty] var properties: [AnyNodeProperty] init(mask: Mask) { self.mode = mask.mode self.inverted = mask.inverted self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes)) self.shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes)) self.expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes)) self.propertyMap = [ "Opacity" : opacity, "Shape" : shape, "Expansion" : expansion ] self.properties = Array(self.propertyMap.values) } let mode: MaskMode let inverted: Bool let opacity: NodeProperty let shape: NodeProperty let expansion: NodeProperty }