mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 13:15:35 +03:00
update classes...20%
This commit is contained in:
parent
10766c3403
commit
536bc08709
@ -1077,7 +1077,10 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.exyte.MacawTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos10.3;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_VERSION = 3.0;
|
||||
VALID_ARCHS = "arm64 armv7 armv7s";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1089,7 +1092,10 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.exyte.MacawTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos10.3;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_VERSION = 3.0;
|
||||
VALID_ARCHS = "arm64 armv7 armv7s";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -52,12 +52,12 @@ class ImageBoundsTests: XCTestCase {
|
||||
|
||||
func testInMemoryImage() {
|
||||
let bundle = Bundle(for: type(of: TestUtils()))
|
||||
guard let uiImage = UIImage(named: "logo.png", in: bundle, compatibleWith: .none) else {
|
||||
guard let mImage = MImage(named: "logo.png", in: bundle, compatibleWith: .none) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
let image = Image(image: uiImage)
|
||||
let image = Image(image: mImage)
|
||||
guard let bounds = image.bounds() else {
|
||||
XCTFail("Bounds not available")
|
||||
return
|
||||
|
@ -144,7 +144,7 @@ class NodeBoundsTests: XCTestCase {
|
||||
texts.forEach { text in
|
||||
let text = Text(text: text)
|
||||
|
||||
let stringAttributes = [NSFontAttributeName: UIFont.systemFont(ofSize: UIFont.systemFontSize)]
|
||||
let stringAttributes = [NSFontAttributeName: MFont.systemFont(ofSize: MFont.systemFontSize)]
|
||||
let size = text.text.size(attributes: stringAttributes)
|
||||
let targetRect = Rect(x: 0.0, y: 0.0, w: size.width.doubleValue, h: size.height.doubleValue)
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
open class AnimatableVariable<T>: Variable<T> {
|
||||
weak internal var node: Node?
|
||||
weak internal var node: Node?
|
||||
}
|
||||
|
@ -1,53 +1,53 @@
|
||||
public enum AnimationState {
|
||||
case initial
|
||||
case running
|
||||
case paused
|
||||
case initial
|
||||
case running
|
||||
case paused
|
||||
}
|
||||
|
||||
public class Animation {
|
||||
|
||||
internal init() {
|
||||
}
|
||||
|
||||
public func play() {
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
|
||||
}
|
||||
|
||||
internal init() {
|
||||
}
|
||||
|
||||
public func play() {
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
|
||||
public func state() -> AnimationState {
|
||||
return .initial
|
||||
}
|
||||
|
||||
public func easing(_ easing: Easing) -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func delay(_ delay: Double) -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func cycle(_ count: Double) -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func cycle() -> Animation{
|
||||
return self
|
||||
}
|
||||
|
||||
public func reverse() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func autoreversed() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult public func onComplete(_: @escaping (() -> ())) -> Animation {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public func state() -> AnimationState {
|
||||
return .initial
|
||||
}
|
||||
|
||||
public func easing(_ easing: Easing) -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func delay(_ delay: Double) -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func cycle(_ count: Double) -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func cycle() -> Animation{
|
||||
return self
|
||||
}
|
||||
|
||||
public func reverse() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func autoreversed() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult public func onComplete(_: @escaping (() -> ())) -> Animation {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -9,266 +9,266 @@
|
||||
import Foundation
|
||||
|
||||
enum AnimationType {
|
||||
case unknown
|
||||
case contents
|
||||
case affineTransformation
|
||||
case opacity
|
||||
case sequence
|
||||
case combine
|
||||
case morphing
|
||||
case shape
|
||||
case empty
|
||||
case unknown
|
||||
case contents
|
||||
case affineTransformation
|
||||
case opacity
|
||||
case sequence
|
||||
case combine
|
||||
case morphing
|
||||
case shape
|
||||
case empty
|
||||
}
|
||||
|
||||
class BasicAnimation: Animation {
|
||||
|
||||
var node: Node?
|
||||
var type = AnimationType.unknown
|
||||
let ID: String
|
||||
var next: BasicAnimation?
|
||||
var removeFunc: (() -> ())?
|
||||
var delayed = false
|
||||
var manualStop = false
|
||||
var paused = false
|
||||
var pausedProgress = 0.0
|
||||
var progress = 0.0
|
||||
var repeatCount = 0.0
|
||||
var cycled = false
|
||||
var delay = 0.0
|
||||
var autoreverses = false
|
||||
var onProgressUpdate: ((Double) -> ())?
|
||||
var easing = Easing.ease
|
||||
var completion: (() -> ())?
|
||||
|
||||
override init() {
|
||||
ID = UUID().uuidString
|
||||
super.init()
|
||||
}
|
||||
|
||||
override open func delay(_ delay: Double) -> Animation {
|
||||
self.delay += delay
|
||||
return self
|
||||
}
|
||||
|
||||
override open func cycle(_ count: Double) -> Animation {
|
||||
self.repeatCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
override open func easing(_ easing: Easing) -> Animation {
|
||||
self.easing = easing
|
||||
return self
|
||||
}
|
||||
|
||||
override open func autoreversed() -> Animation {
|
||||
self.autoreverses = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
var node: Node?
|
||||
var type = AnimationType.unknown
|
||||
let ID: String
|
||||
var next: BasicAnimation?
|
||||
var removeFunc: (() -> ())?
|
||||
var delayed = false
|
||||
var manualStop = false
|
||||
var paused = false
|
||||
var pausedProgress = 0.0
|
||||
var progress = 0.0
|
||||
var repeatCount = 0.0
|
||||
var cycled = false
|
||||
var delay = 0.0
|
||||
var autoreverses = false
|
||||
var onProgressUpdate: ((Double) -> ())?
|
||||
var easing = Easing.ease
|
||||
var completion: (() -> ())?
|
||||
|
||||
override init() {
|
||||
ID = UUID().uuidString
|
||||
super.init()
|
||||
}
|
||||
|
||||
override open func delay(_ delay: Double) -> Animation {
|
||||
self.delay += delay
|
||||
return self
|
||||
}
|
||||
|
||||
override open func cycle(_ count: Double) -> Animation {
|
||||
self.repeatCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
override open func easing(_ easing: Easing) -> Animation {
|
||||
self.easing = easing
|
||||
return self
|
||||
}
|
||||
|
||||
override open func autoreversed() -> Animation {
|
||||
self.autoreverses = true
|
||||
|
||||
override open func cycle() -> Animation {
|
||||
self.cycled = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
override open func onComplete(_ f: @escaping (() -> ())) -> Animation {
|
||||
self.completion = f
|
||||
return self
|
||||
}
|
||||
|
||||
override open func play() {
|
||||
|
||||
manualStop = false
|
||||
paused = false
|
||||
|
||||
animationProducer.addAnimation(self)
|
||||
}
|
||||
|
||||
override open func stop() {
|
||||
manualStop = true
|
||||
paused = false
|
||||
|
||||
if delay > 0.0 {
|
||||
animationProducer.removeDelayed(animation: self)
|
||||
}
|
||||
|
||||
removeFunc?()
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
override open func cycle() -> Animation {
|
||||
self.cycled = true
|
||||
|
||||
override open func pause() {
|
||||
paused = true
|
||||
manualStop = false
|
||||
|
||||
if delay > 0.0 {
|
||||
animationProducer.removeDelayed(animation: self)
|
||||
}
|
||||
|
||||
removeFunc?()
|
||||
return self
|
||||
}
|
||||
|
||||
override open func onComplete(_ f: @escaping (() -> ())) -> Animation {
|
||||
self.completion = f
|
||||
return self
|
||||
}
|
||||
|
||||
override open func play() {
|
||||
|
||||
manualStop = false
|
||||
paused = false
|
||||
|
||||
animationProducer.addAnimation(self)
|
||||
}
|
||||
|
||||
override open func stop() {
|
||||
manualStop = true
|
||||
paused = false
|
||||
|
||||
if delay > 0.0 {
|
||||
animationProducer.removeDelayed(animation: self)
|
||||
}
|
||||
|
||||
override func state() -> AnimationState {
|
||||
if delayed {
|
||||
return .running
|
||||
}
|
||||
|
||||
if self.progress == 0.0 {
|
||||
return .initial
|
||||
}
|
||||
|
||||
if paused || manualStop || progress == 1.0 {
|
||||
return .paused
|
||||
}
|
||||
|
||||
return .running
|
||||
removeFunc?()
|
||||
}
|
||||
|
||||
override open func pause() {
|
||||
paused = true
|
||||
manualStop = false
|
||||
|
||||
if delay > 0.0 {
|
||||
animationProducer.removeDelayed(animation: self)
|
||||
}
|
||||
|
||||
override open func reverse() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
func getDuration() -> Double { return 0 }
|
||||
|
||||
removeFunc?()
|
||||
}
|
||||
|
||||
override func state() -> AnimationState {
|
||||
if delayed {
|
||||
return .running
|
||||
}
|
||||
|
||||
if self.progress == 0.0 {
|
||||
return .initial
|
||||
}
|
||||
|
||||
if paused || manualStop || progress == 1.0 {
|
||||
return .paused
|
||||
}
|
||||
|
||||
return .running
|
||||
}
|
||||
|
||||
override open func reverse() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
func getDuration() -> Double { return 0 }
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
extension BasicAnimation: Hashable {
|
||||
public var hashValue: Int {
|
||||
return ID.hashValue
|
||||
}
|
||||
|
||||
public static func ==(lhs: BasicAnimation, rhs: BasicAnimation) -> Bool {
|
||||
return lhs.ID == rhs.ID
|
||||
}
|
||||
public var hashValue: Int {
|
||||
return ID.hashValue
|
||||
}
|
||||
|
||||
public static func ==(lhs: BasicAnimation, rhs: BasicAnimation) -> Bool {
|
||||
return lhs.ID == rhs.ID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Activity
|
||||
extension BasicAnimation {
|
||||
func isActive() -> Bool {
|
||||
return progress > 0.0 && progress < 1.0
|
||||
}
|
||||
func isActive() -> Bool {
|
||||
return progress > 0.0 && progress < 1.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Animated property list https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/AnimatableProperties/AnimatableProperties.html
|
||||
internal class AnimationImpl<T: Interpolable>: BasicAnimation {
|
||||
|
||||
var variable: AnimatableVariable<T>
|
||||
let initialValue: T
|
||||
let timeFactory: (() -> ((Double) -> T))
|
||||
let duration: Double
|
||||
let logicalFps: UInt
|
||||
|
||||
private var vFunc: ((Double) -> T)?
|
||||
|
||||
init(observableValue: AnimatableVariable<T>, valueFunc: @escaping (Double) -> T, animationDuration: Double, delay: Double = 0.0, fps: UInt = 30) {
|
||||
self.variable = observableValue
|
||||
self.initialValue = observableValue.value
|
||||
self.duration = animationDuration
|
||||
self.timeFactory = { (node) in return valueFunc }
|
||||
self.vFunc = .none
|
||||
self.logicalFps = fps
|
||||
|
||||
super.init()
|
||||
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
var variable: AnimatableVariable<T>
|
||||
let initialValue: T
|
||||
let timeFactory: (() -> ((Double) -> T))
|
||||
let duration: Double
|
||||
let logicalFps: UInt
|
||||
|
||||
private var vFunc: ((Double) -> T)?
|
||||
|
||||
init(observableValue: AnimatableVariable<T>, valueFunc: @escaping (Double) -> T, animationDuration: Double, delay: Double = 0.0, fps: UInt = 30) {
|
||||
self.variable = observableValue
|
||||
self.initialValue = observableValue.value
|
||||
self.duration = animationDuration
|
||||
self.timeFactory = { (node) in return valueFunc }
|
||||
self.vFunc = .none
|
||||
self.logicalFps = fps
|
||||
|
||||
init(observableValue: AnimatableVariable<T>, factory: @escaping (() -> ((Double) -> T)), animationDuration: Double, delay: Double = 0.0, fps: UInt = 30) {
|
||||
self.variable = observableValue
|
||||
self.initialValue = observableValue.value
|
||||
self.duration = animationDuration
|
||||
self.timeFactory = factory
|
||||
self.logicalFps = fps
|
||||
|
||||
super.init()
|
||||
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
convenience init(observableValue: AnimatableVariable<T>, startValue: T, finalValue: T, animationDuration: Double) {
|
||||
let interpolationFunc = { (t: Double) -> T in
|
||||
return startValue.interpolate(finalValue, progress: t)
|
||||
}
|
||||
|
||||
self.init(observableValue: observableValue, valueFunc: interpolationFunc, animationDuration: animationDuration)
|
||||
}
|
||||
|
||||
convenience init(observableValue: AnimatableVariable<T>, finalValue: T, animationDuration: Double) {
|
||||
self.init(observableValue: observableValue, startValue: observableValue.value, finalValue: finalValue, animationDuration: animationDuration)
|
||||
}
|
||||
super.init()
|
||||
|
||||
override open func play() {
|
||||
|
||||
if manualStop {
|
||||
variable.value = getVFunc()(0.0)
|
||||
}
|
||||
|
||||
if paused {
|
||||
variable.value = getVFunc()(pausedProgress)
|
||||
}
|
||||
|
||||
super.play()
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
init(observableValue: AnimatableVariable<T>, factory: @escaping (() -> ((Double) -> T)), animationDuration: Double, delay: Double = 0.0, fps: UInt = 30) {
|
||||
self.variable = observableValue
|
||||
self.initialValue = observableValue.value
|
||||
self.duration = animationDuration
|
||||
self.timeFactory = factory
|
||||
self.logicalFps = fps
|
||||
|
||||
super.init()
|
||||
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
convenience init(observableValue: AnimatableVariable<T>, startValue: T, finalValue: T, animationDuration: Double) {
|
||||
let interpolationFunc = { (t: Double) -> T in
|
||||
return startValue.interpolate(finalValue, progress: t)
|
||||
}
|
||||
|
||||
open override func getDuration() -> Double {
|
||||
var totalDuration = autoreverses ? duration * 2.0 : duration
|
||||
totalDuration = totalDuration * (1.0 - pausedProgress)
|
||||
|
||||
return totalDuration
|
||||
}
|
||||
|
||||
open func getVFunc() -> ((Double) -> T) {
|
||||
if let vFunc = vFunc {
|
||||
return vFunc
|
||||
}
|
||||
|
||||
var timeFunc = { (t: Double) -> Double in
|
||||
return t
|
||||
}
|
||||
|
||||
if autoreverses {
|
||||
let original = timeFunc
|
||||
timeFunc = { (t: Double) -> Double in
|
||||
if t <= 0.5 {
|
||||
return original(t * 2.0)
|
||||
} else {
|
||||
return original((1.0 - t) * 2.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vFunc = { (t: Double) -> T in
|
||||
return self.timeFactory()(timeFunc(t))
|
||||
}
|
||||
|
||||
return vFunc!
|
||||
|
||||
self.init(observableValue: observableValue, valueFunc: interpolationFunc, animationDuration: animationDuration)
|
||||
}
|
||||
|
||||
convenience init(observableValue: AnimatableVariable<T>, finalValue: T, animationDuration: Double) {
|
||||
self.init(observableValue: observableValue, startValue: observableValue.value, finalValue: finalValue, animationDuration: animationDuration)
|
||||
}
|
||||
|
||||
override open func play() {
|
||||
|
||||
if manualStop {
|
||||
variable.value = getVFunc()(0.0)
|
||||
}
|
||||
|
||||
|
||||
if paused {
|
||||
variable.value = getVFunc()(pausedProgress)
|
||||
}
|
||||
|
||||
super.play()
|
||||
}
|
||||
|
||||
open override func getDuration() -> Double {
|
||||
var totalDuration = autoreverses ? duration * 2.0 : duration
|
||||
totalDuration = totalDuration * (1.0 - pausedProgress)
|
||||
|
||||
return totalDuration
|
||||
}
|
||||
|
||||
open func getVFunc() -> ((Double) -> T) {
|
||||
if let vFunc = vFunc {
|
||||
return vFunc
|
||||
}
|
||||
|
||||
var timeFunc = { (t: Double) -> Double in
|
||||
return t
|
||||
}
|
||||
|
||||
if autoreverses {
|
||||
let original = timeFunc
|
||||
timeFunc = { (t: Double) -> Double in
|
||||
if t <= 0.5 {
|
||||
return original(t * 2.0)
|
||||
} else {
|
||||
return original((1.0 - t) * 2.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vFunc = { (t: Double) -> T in
|
||||
return self.timeFactory()(timeFunc(t))
|
||||
}
|
||||
|
||||
return vFunc!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// For sequence completion
|
||||
class EmptyAnimation: BasicAnimation {
|
||||
required init(completion: @escaping (() -> ())) {
|
||||
super.init()
|
||||
|
||||
self.completion = completion
|
||||
self.type = .empty
|
||||
}
|
||||
required init(completion: @escaping (() -> ())) {
|
||||
super.init()
|
||||
|
||||
self.completion = completion
|
||||
self.type = .empty
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation Description
|
||||
|
||||
open class AnimationDescription <T> {
|
||||
open let valueFunc: (Double) -> T
|
||||
open var duration = 0.0
|
||||
open var delay = 0.0
|
||||
public init(valueFunc: @escaping (Double) -> T, duration: Double = 1.0, delay: Double = 0.0) {
|
||||
self.valueFunc = valueFunc
|
||||
self.duration = duration
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
open func t(_ duration: Double, delay: Double = 0.0) -> AnimationDescription<T> {
|
||||
return AnimationDescription(valueFunc: valueFunc, duration: duration, delay: delay)
|
||||
}
|
||||
open let valueFunc: (Double) -> T
|
||||
open var duration = 0.0
|
||||
open var delay = 0.0
|
||||
public init(valueFunc: @escaping (Double) -> T, duration: Double = 1.0, delay: Double = 0.0) {
|
||||
self.valueFunc = valueFunc
|
||||
self.duration = duration
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
open func t(_ duration: Double, delay: Double = 0.0) -> AnimationDescription<T> {
|
||||
return AnimationDescription(valueFunc: valueFunc, duration: duration, delay: delay)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
@ -5,387 +7,386 @@
|
||||
let animationProducer = AnimationProducer()
|
||||
|
||||
class AnimationProducer {
|
||||
|
||||
|
||||
var storedAnimations = [Node: BasicAnimation]()
|
||||
var delayedAnimations = [BasicAnimation: Timer]()
|
||||
var displayLink: MDisplayLink?
|
||||
|
||||
struct ContentAnimationDesc {
|
||||
let animation: ContentsAnimation
|
||||
let layer: CALayer
|
||||
weak var cache: AnimationCache?
|
||||
let startDate: Date
|
||||
let finishDate: Date
|
||||
let completion: (()->())?
|
||||
}
|
||||
|
||||
var contentsAnimations = [ContentAnimationDesc]()
|
||||
|
||||
func addAnimation(_ animation: BasicAnimation, withoutDelay: Bool = false) {
|
||||
|
||||
var storedAnimations = [Node: BasicAnimation]()
|
||||
var delayedAnimations = [BasicAnimation: Timer]()
|
||||
var displayLink: CADisplayLink?
|
||||
|
||||
struct ContentAnimationDesc {
|
||||
let animation: ContentsAnimation
|
||||
let layer: CALayer
|
||||
weak var cache: AnimationCache?
|
||||
let startDate: Date
|
||||
let finishDate: Date
|
||||
let completion: (()->())?
|
||||
}
|
||||
|
||||
var contentsAnimations = [ContentAnimationDesc]()
|
||||
|
||||
func addAnimation(_ animation: BasicAnimation, withoutDelay: Bool = false) {
|
||||
|
||||
// Delay - launching timer
|
||||
if animation.delay > 0.0 && !withoutDelay {
|
||||
|
||||
let timer = Timer.schedule(delay: animation.delay, handler: { [weak self] _ in
|
||||
self?.addAnimation(animation, withoutDelay: true)
|
||||
_ = self?.delayedAnimations.removeValue(forKey: animation)
|
||||
animation.delayed = false
|
||||
})
|
||||
|
||||
animation.delayed = true
|
||||
delayedAnimations[animation] = timer
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Empty - executing completion
|
||||
if animation.type == .empty {
|
||||
executeCompletion(animation)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle - attaching "re-add animation" logic
|
||||
if animation.cycled {
|
||||
if animation.manualStop {
|
||||
return
|
||||
}
|
||||
|
||||
let reAdd = EmptyAnimation {
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
|
||||
if let nextAnimation = animation.next {
|
||||
nextAnimation.next = reAdd
|
||||
} else {
|
||||
animation.next = reAdd
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// General case
|
||||
guard let node = animation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let macawView = nodesMap.getView(node) else {
|
||||
storedAnimations[node] = animation
|
||||
return
|
||||
}
|
||||
|
||||
guard let cache = macawView.animationCache else {
|
||||
return
|
||||
}
|
||||
|
||||
switch animation.type {
|
||||
case .unknown:
|
||||
return
|
||||
case .affineTransformation:
|
||||
addTransformAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
|
||||
case .opacity:
|
||||
addOpacityAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .sequence:
|
||||
addAnimationSequence(animation)
|
||||
case .combine:
|
||||
addCombineAnimation(animation)
|
||||
case .contents:
|
||||
addContentsAnimation(animation, cache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .morphing:
|
||||
addMorphingAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .shape:
|
||||
addShapeAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .empty:
|
||||
executeCompletion(animation)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDelayed(animation: BasicAnimation) {
|
||||
guard let timer = delayedAnimations[animation] else {
|
||||
return
|
||||
}
|
||||
|
||||
timer.invalidate()
|
||||
|
||||
// Delay - launching timer
|
||||
if animation.delay > 0.0 && !withoutDelay {
|
||||
|
||||
let timer = Timer.schedule(delay: animation.delay, handler: { [weak self] _ in
|
||||
self?.addAnimation(animation, withoutDelay: true)
|
||||
_ = self?.delayedAnimations.removeValue(forKey: animation)
|
||||
animation.delayed = false
|
||||
delayedAnimations.removeValue(forKey: animation)
|
||||
})
|
||||
|
||||
animation.delayed = true
|
||||
delayedAnimations[animation] = timer
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MARK: - Sequence animation
|
||||
fileprivate func addAnimationSequence(_ animationSequnce: Animation) {
|
||||
guard let sequence = animationSequnce as? AnimationSequence else {
|
||||
return
|
||||
}
|
||||
|
||||
// Generating sequence
|
||||
var sequenceAnimations = [BasicAnimation]()
|
||||
var cycleAnimations = sequence.animations
|
||||
|
||||
if sequence.autoreverses {
|
||||
cycleAnimations.append(contentsOf: sequence.animations.reversed())
|
||||
}
|
||||
|
||||
if sequence.repeatCount > 0.0001 {
|
||||
for _ in 0..<Int(sequence.repeatCount) {
|
||||
sequenceAnimations.append(contentsOf: cycleAnimations)
|
||||
}
|
||||
} else {
|
||||
sequenceAnimations.append(contentsOf: cycleAnimations)
|
||||
}
|
||||
|
||||
// Connecting animations
|
||||
for i in 0..<(sequenceAnimations.count - 1) {
|
||||
let animation = sequenceAnimations[i]
|
||||
animation.next = sequenceAnimations[i + 1]
|
||||
}
|
||||
|
||||
// Completion
|
||||
if let completion = sequence.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
|
||||
if let next = sequence.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
sequenceAnimations.last?.next = completionAnimation
|
||||
} else {
|
||||
if let next = sequence.next {
|
||||
sequenceAnimations.last?.next = next
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
if let firstAnimation = sequence.animations.first {
|
||||
self.addAnimation(firstAnimation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Combine animation
|
||||
fileprivate func addCombineAnimation(_ combineAnimation: Animation) {
|
||||
guard let combine = combineAnimation as? CombineAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
// Reversing
|
||||
if combine.autoreverses {
|
||||
combine.animations.forEach { animation in
|
||||
animation.autoreverses = true
|
||||
}
|
||||
}
|
||||
|
||||
// repeat count
|
||||
if combine.repeatCount > 0.00001 {
|
||||
var sequence = [Animation]()
|
||||
|
||||
for _ in 0..<Int(combine.repeatCount) {
|
||||
sequence.append(combine)
|
||||
}
|
||||
|
||||
combine.repeatCount = 0.0
|
||||
addAnimationSequence(sequence.sequence())
|
||||
return
|
||||
}
|
||||
|
||||
// Looking for longest animation
|
||||
var longestAnimation: BasicAnimation?
|
||||
combine.animations.forEach { animation in
|
||||
guard let longest = longestAnimation else {
|
||||
longestAnimation = animation
|
||||
return
|
||||
}
|
||||
|
||||
if longest.getDuration() < animation.getDuration() {
|
||||
longestAnimation = animation
|
||||
}
|
||||
}
|
||||
|
||||
// Attaching completion empty animation and potential next animation
|
||||
if let completion = combine.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
if let next = combine.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
longestAnimation?.next = completionAnimation
|
||||
|
||||
} else {
|
||||
if let next = combine.next {
|
||||
longestAnimation?.next = next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
combine.removeFunc = {
|
||||
combine.animations.forEach { animation in
|
||||
animation.removeFunc?()
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
combine.animations.forEach { animation in
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Empty Animation
|
||||
fileprivate func executeCompletion(_ emptyAnimation: BasicAnimation) {
|
||||
emptyAnimation.completion?()
|
||||
}
|
||||
|
||||
// MARK: - Stored animation
|
||||
func addStoredAnimations(_ node: Node) {
|
||||
if let animation = storedAnimations[node] {
|
||||
addAnimation(animation)
|
||||
storedAnimations.removeValue(forKey: node)
|
||||
}
|
||||
|
||||
guard let group = node as? Group else {
|
||||
return
|
||||
}
|
||||
|
||||
group.contents.forEach { child in
|
||||
addStoredAnimations(child)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Contents animation
|
||||
|
||||
func addContentsAnimation(_ animation: BasicAnimation, cache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let contentsAnimation = animation as? ContentsAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = contentsAnimation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
if animation.autoreverses {
|
||||
animation.autoreverses = false
|
||||
addAnimation([animation, animation.reverse()].sequence() as! BasicAnimation)
|
||||
return
|
||||
}
|
||||
|
||||
if animation.repeatCount > 0.0001 {
|
||||
animation.repeatCount = 0.0
|
||||
var animSequence = [Animation]()
|
||||
for _ in 0...Int(animation.repeatCount) {
|
||||
animSequence.append(animation)
|
||||
}
|
||||
|
||||
addAnimation(animSequence.sequence() as! BasicAnimation)
|
||||
return
|
||||
}
|
||||
|
||||
let startDate = Date(timeInterval: contentsAnimation.delay, since: Date())
|
||||
|
||||
var unionBounds: Rect? = .none
|
||||
if let startBounds = contentsAnimation.getVFunc()(0.0).group().bounds(),
|
||||
let endBounds = contentsAnimation.getVFunc()(1.0).group().bounds() {
|
||||
unionBounds = startBounds.union(rect: endBounds)
|
||||
}
|
||||
|
||||
guard let layer = cache?.layerForNode(node, animation: contentsAnimation, customBounds: unionBounds) else {
|
||||
return
|
||||
}
|
||||
|
||||
let animationDesc = ContentAnimationDesc(
|
||||
animation: contentsAnimation,
|
||||
layer: layer,
|
||||
cache: cache,
|
||||
startDate: Date(),
|
||||
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
|
||||
completion: completion
|
||||
)
|
||||
|
||||
contentsAnimations.append(animationDesc)
|
||||
|
||||
if displayLink == .none {
|
||||
displayLink = CADisplayLink(target: self, selector: #selector(updateContentAnimations))
|
||||
displayLink?.frameInterval = 1
|
||||
displayLink?.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
}
|
||||
// Empty - executing completion
|
||||
if animation.type == .empty {
|
||||
executeCompletion(animation)
|
||||
return
|
||||
}
|
||||
|
||||
@objc func updateContentAnimations() {
|
||||
if contentsAnimations.count == 0 {
|
||||
displayLink?.invalidate()
|
||||
displayLink = .none
|
||||
// Cycle - attaching "re-add animation" logic
|
||||
if animation.cycled {
|
||||
if animation.manualStop {
|
||||
return
|
||||
}
|
||||
|
||||
let reAdd = EmptyAnimation {
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
|
||||
if let nextAnimation = animation.next {
|
||||
nextAnimation.next = reAdd
|
||||
} else {
|
||||
animation.next = reAdd
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// General case
|
||||
guard let node = animation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let macawView = nodesMap.getView(node) else {
|
||||
storedAnimations[node] = animation
|
||||
return
|
||||
}
|
||||
|
||||
guard let cache = macawView.animationCache else {
|
||||
return
|
||||
}
|
||||
|
||||
switch animation.type {
|
||||
case .unknown:
|
||||
return
|
||||
case .affineTransformation:
|
||||
addTransformAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
|
||||
case .opacity:
|
||||
addOpacityAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .sequence:
|
||||
addAnimationSequence(animation)
|
||||
case .combine:
|
||||
addCombineAnimation(animation)
|
||||
case .contents:
|
||||
addContentsAnimation(animation, cache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .morphing:
|
||||
addMorphingAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .shape:
|
||||
addShapeAnimation(animation, sceneLayer: macawView.layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .empty:
|
||||
executeCompletion(animation)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDelayed(animation: BasicAnimation) {
|
||||
guard let timer = delayedAnimations[animation] else {
|
||||
return
|
||||
}
|
||||
|
||||
timer.invalidate()
|
||||
|
||||
animation.delayed = false
|
||||
delayedAnimations.removeValue(forKey: animation)
|
||||
}
|
||||
|
||||
// MARK: - Sequence animation
|
||||
fileprivate func addAnimationSequence(_ animationSequnce: Animation) {
|
||||
guard let sequence = animationSequnce as? AnimationSequence else {
|
||||
return
|
||||
}
|
||||
|
||||
// Generating sequence
|
||||
var sequenceAnimations = [BasicAnimation]()
|
||||
var cycleAnimations = sequence.animations
|
||||
|
||||
if sequence.autoreverses {
|
||||
cycleAnimations.append(contentsOf: sequence.animations.reversed())
|
||||
}
|
||||
|
||||
if sequence.repeatCount > 0.0001 {
|
||||
for _ in 0..<Int(sequence.repeatCount) {
|
||||
sequenceAnimations.append(contentsOf: cycleAnimations)
|
||||
}
|
||||
} else {
|
||||
sequenceAnimations.append(contentsOf: cycleAnimations)
|
||||
}
|
||||
|
||||
// Connecting animations
|
||||
for i in 0..<(sequenceAnimations.count - 1) {
|
||||
let animation = sequenceAnimations[i]
|
||||
animation.next = sequenceAnimations[i + 1]
|
||||
}
|
||||
|
||||
// Completion
|
||||
if let completion = sequence.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
|
||||
if let next = sequence.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
sequenceAnimations.last?.next = completionAnimation
|
||||
} else {
|
||||
if let next = sequence.next {
|
||||
sequenceAnimations.last?.next = next
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
if let firstAnimation = sequence.animations.first {
|
||||
self.addAnimation(firstAnimation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Combine animation
|
||||
fileprivate func addCombineAnimation(_ combineAnimation: Animation) {
|
||||
guard let combine = combineAnimation as? CombineAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
// Reversing
|
||||
if combine.autoreverses {
|
||||
combine.animations.forEach { animation in
|
||||
animation.autoreverses = true
|
||||
}
|
||||
}
|
||||
|
||||
// repeat count
|
||||
if combine.repeatCount > 0.00001 {
|
||||
var sequence = [Animation]()
|
||||
|
||||
for _ in 0..<Int(combine.repeatCount) {
|
||||
sequence.append(combine)
|
||||
}
|
||||
|
||||
combine.repeatCount = 0.0
|
||||
addAnimationSequence(sequence.sequence())
|
||||
return
|
||||
}
|
||||
|
||||
// Looking for longest animation
|
||||
var longestAnimation: BasicAnimation?
|
||||
combine.animations.forEach { animation in
|
||||
guard let longest = longestAnimation else {
|
||||
longestAnimation = animation
|
||||
return
|
||||
}
|
||||
|
||||
if longest.getDuration() < animation.getDuration() {
|
||||
longestAnimation = animation
|
||||
}
|
||||
}
|
||||
|
||||
// Attaching completion empty animation and potential next animation
|
||||
if let completion = combine.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
if let next = combine.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
longestAnimation?.next = completionAnimation
|
||||
|
||||
} else {
|
||||
if let next = combine.next {
|
||||
longestAnimation?.next = next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
combine.removeFunc = {
|
||||
combine.animations.forEach { animation in
|
||||
animation.removeFunc?()
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
combine.animations.forEach { animation in
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Empty Animation
|
||||
fileprivate func executeCompletion(_ emptyAnimation: BasicAnimation) {
|
||||
emptyAnimation.completion?()
|
||||
}
|
||||
|
||||
// MARK: - Stored animation
|
||||
func addStoredAnimations(_ node: Node) {
|
||||
if let animation = storedAnimations[node] {
|
||||
addAnimation(animation)
|
||||
storedAnimations.removeValue(forKey: node)
|
||||
}
|
||||
|
||||
guard let group = node as? Group else {
|
||||
return
|
||||
}
|
||||
|
||||
group.contents.forEach { child in
|
||||
addStoredAnimations(child)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Contents animation
|
||||
|
||||
func addContentsAnimation(_ animation: BasicAnimation, cache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let contentsAnimation = animation as? ContentsAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = contentsAnimation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
if animation.autoreverses {
|
||||
animation.autoreverses = false
|
||||
addAnimation([animation, animation.reverse()].sequence() as! BasicAnimation)
|
||||
return
|
||||
}
|
||||
|
||||
if animation.repeatCount > 0.0001 {
|
||||
animation.repeatCount = 0.0
|
||||
var animSequence = [Animation]()
|
||||
for _ in 0...Int(animation.repeatCount) {
|
||||
animSequence.append(animation)
|
||||
}
|
||||
|
||||
addAnimation(animSequence.sequence() as! BasicAnimation)
|
||||
return
|
||||
}
|
||||
|
||||
let startDate = Date(timeInterval: contentsAnimation.delay, since: Date())
|
||||
|
||||
var unionBounds: Rect? = .none
|
||||
if let startBounds = contentsAnimation.getVFunc()(0.0).group().bounds(),
|
||||
let endBounds = contentsAnimation.getVFunc()(1.0).group().bounds() {
|
||||
unionBounds = startBounds.union(rect: endBounds)
|
||||
}
|
||||
|
||||
guard let layer = cache?.layerForNode(node, animation: contentsAnimation, customBounds: unionBounds) else {
|
||||
return
|
||||
}
|
||||
|
||||
let animationDesc = ContentAnimationDesc(
|
||||
animation: contentsAnimation,
|
||||
layer: layer,
|
||||
cache: cache,
|
||||
startDate: Date(),
|
||||
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
|
||||
completion: completion
|
||||
)
|
||||
|
||||
contentsAnimations.append(animationDesc)
|
||||
|
||||
if displayLink == .none {
|
||||
displayLink = CADisplayLink(target: self, selector: #selector(updateContentAnimations))
|
||||
displayLink?.frameInterval = 1
|
||||
displayLink?.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func updateContentAnimations() {
|
||||
if contentsAnimations.count == 0 {
|
||||
displayLink?.invalidate()
|
||||
displayLink = .none
|
||||
}
|
||||
|
||||
let currentDate = Date()
|
||||
var animationsToRemove = [Animation]()
|
||||
let count = contentsAnimations.count
|
||||
for (index, animationDesc) in contentsAnimations.reversed().enumerated() {
|
||||
|
||||
let animation = animationDesc.animation
|
||||
guard let group = animation.node as? Group else {
|
||||
continue
|
||||
}
|
||||
|
||||
defer {
|
||||
animationDesc.layer.setNeedsDisplay()
|
||||
animationDesc.layer.displayIfNeeded()
|
||||
}
|
||||
|
||||
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
|
||||
|
||||
// Completion
|
||||
if progress >= 1.0 {
|
||||
|
||||
// Final update
|
||||
group.contents = animation.getVFunc()(1.0)
|
||||
animation.onProgressUpdate?(1.0)
|
||||
animation.pausedProgress = 0.0
|
||||
|
||||
// Finishing animation
|
||||
if !animation.cycled {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
let currentDate = Date()
|
||||
var animationsToRemove = [Animation]()
|
||||
let count = contentsAnimations.count
|
||||
for (index, animationDesc) in contentsAnimations.reversed().enumerated() {
|
||||
|
||||
let animation = animationDesc.animation
|
||||
guard let group = animation.node as? Group else {
|
||||
continue
|
||||
}
|
||||
|
||||
defer {
|
||||
animationDesc.layer.setNeedsDisplay()
|
||||
animationDesc.layer.displayIfNeeded()
|
||||
}
|
||||
|
||||
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
|
||||
|
||||
// Completion
|
||||
if progress >= 1.0 {
|
||||
|
||||
// Final update
|
||||
group.contents = animation.getVFunc()(1.0)
|
||||
animation.onProgressUpdate?(1.0)
|
||||
animation.pausedProgress = 0.0
|
||||
|
||||
// Finishing animation
|
||||
if !animation.cycled {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
contentsAnimations.remove(at: count - 1 - index)
|
||||
animationDesc.cache?.freeLayer(group)
|
||||
animationDesc.completion?()
|
||||
continue
|
||||
}
|
||||
|
||||
let t = progressForTimingFunction(animation.easing, progress: progress)
|
||||
group.contents = animation.getVFunc()(t)
|
||||
animation.onProgressUpdate?(progress)
|
||||
|
||||
// Manual stop
|
||||
if animation.manualStop || animation.paused {
|
||||
defer {
|
||||
contentsAnimations.remove(at: count - 1 - index)
|
||||
animationDesc.cache?.freeLayer(group)
|
||||
}
|
||||
|
||||
if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
group.contents = animation.getVFunc()(0)
|
||||
} else if animation.paused {
|
||||
animation.pausedProgress = progress
|
||||
}
|
||||
}
|
||||
contentsAnimations.remove(at: count - 1 - index)
|
||||
animationDesc.cache?.freeLayer(group)
|
||||
animationDesc.completion?()
|
||||
continue
|
||||
}
|
||||
|
||||
let t = progressForTimingFunction(animation.easing, progress: progress)
|
||||
group.contents = animation.getVFunc()(t)
|
||||
animation.onProgressUpdate?(progress)
|
||||
|
||||
// Manual stop
|
||||
if animation.manualStop || animation.paused {
|
||||
defer {
|
||||
contentsAnimations.remove(at: count - 1 - index)
|
||||
animationDesc.cache?.freeLayer(group)
|
||||
}
|
||||
|
||||
if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
group.contents = animation.getVFunc()(0)
|
||||
} else if animation.paused {
|
||||
animation.pausedProgress = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +1,103 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AnimationUtils {
|
||||
class func absolutePosition(_ node: Node) -> Transform {
|
||||
return AnimationUtils.absoluteTransform(node, pos: node.place)
|
||||
}
|
||||
|
||||
class func absoluteTransform(_ node: Node, pos: Transform) -> Transform {
|
||||
var transform = pos
|
||||
var parent = nodesMap.parents(node).first
|
||||
while parent != .none {
|
||||
transform = GeomUtils.concat(t1: parent!.place, t2: transform)
|
||||
parent = nodesMap.parents(parent!).first
|
||||
}
|
||||
|
||||
return transform
|
||||
}
|
||||
|
||||
class func absoluteClip(node: Node) -> Locus? {
|
||||
|
||||
if let _ = node.clip {
|
||||
return node.clip
|
||||
}
|
||||
|
||||
var parent = nodesMap.parents(node).first
|
||||
while parent != .none {
|
||||
if let _ = parent?.clip {
|
||||
return parent?.clip
|
||||
}
|
||||
|
||||
parent = nodesMap.parents(parent!).first
|
||||
}
|
||||
|
||||
return .none
|
||||
class func absolutePosition(_ node: Node) -> Transform {
|
||||
return AnimationUtils.absoluteTransform(node, pos: node.place)
|
||||
}
|
||||
|
||||
class func absoluteTransform(_ node: Node, pos: Transform) -> Transform {
|
||||
var transform = pos
|
||||
var parent = nodesMap.parents(node).first
|
||||
while parent != .none {
|
||||
transform = GeomUtils.concat(t1: parent!.place, t2: transform)
|
||||
parent = nodesMap.parents(parent!).first
|
||||
}
|
||||
|
||||
private static var indexCache = [Node: Int]()
|
||||
class func absoluteIndex(_ node: Node, useCache: Bool = false) -> Int {
|
||||
if useCache {
|
||||
if let cachedIndex = indexCache[node] {
|
||||
return cachedIndex
|
||||
}
|
||||
} else {
|
||||
indexCache.removeAll()
|
||||
}
|
||||
|
||||
|
||||
func childrenTotalCount(_ node: Node) -> Int{
|
||||
guard let group = node as? Group else {
|
||||
return 1
|
||||
}
|
||||
|
||||
var count = 1
|
||||
for child in group.contents {
|
||||
count += childrenTotalCount(child)
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
var zIndex = 0
|
||||
var parent = nodesMap.parents(node).first
|
||||
var currentNode = node
|
||||
while parent != .none {
|
||||
if let group = parent as? Group {
|
||||
let localIndex = group.contents.index(of: currentNode) ?? group.contents.count
|
||||
|
||||
for i in 0..<localIndex {
|
||||
zIndex += childrenTotalCount(group.contents[i])
|
||||
}
|
||||
}
|
||||
|
||||
zIndex += 1
|
||||
|
||||
currentNode = parent!
|
||||
parent = nodesMap.parents(parent!).first
|
||||
}
|
||||
|
||||
if useCache {
|
||||
indexCache[node] = zIndex
|
||||
}
|
||||
|
||||
return zIndex
|
||||
return transform
|
||||
}
|
||||
|
||||
class func absoluteClip(node: Node) -> Locus? {
|
||||
|
||||
if let _ = node.clip {
|
||||
return node.clip
|
||||
}
|
||||
|
||||
class func animatedNodes(root: Node, animationCache: AnimationCache) -> [Node] {
|
||||
if animationCache.isAnimating(root) {
|
||||
return [root]
|
||||
}
|
||||
|
||||
guard let rootGroup = root as? Group else {
|
||||
return []
|
||||
}
|
||||
|
||||
var result = [Node]()
|
||||
rootGroup.contents.forEach { child in
|
||||
let childAnimatedNodes = animatedNodes(root: child, animationCache: animationCache)
|
||||
result.append(contentsOf: childAnimatedNodes)
|
||||
}
|
||||
|
||||
return result
|
||||
var parent = nodesMap.parents(node).first
|
||||
while parent != .none {
|
||||
if let _ = parent?.clip {
|
||||
return parent?.clip
|
||||
}
|
||||
|
||||
parent = nodesMap.parents(parent!).first
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
private static var indexCache = [Node: Int]()
|
||||
class func absoluteIndex(_ node: Node, useCache: Bool = false) -> Int {
|
||||
if useCache {
|
||||
if let cachedIndex = indexCache[node] {
|
||||
return cachedIndex
|
||||
}
|
||||
} else {
|
||||
indexCache.removeAll()
|
||||
}
|
||||
|
||||
|
||||
func childrenTotalCount(_ node: Node) -> Int{
|
||||
guard let group = node as? Group else {
|
||||
return 1
|
||||
}
|
||||
|
||||
var count = 1
|
||||
for child in group.contents {
|
||||
count += childrenTotalCount(child)
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
var zIndex = 0
|
||||
var parent = nodesMap.parents(node).first
|
||||
var currentNode = node
|
||||
while parent != .none {
|
||||
if let group = parent as? Group {
|
||||
let localIndex = group.contents.index(of: currentNode) ?? group.contents.count
|
||||
|
||||
for i in 0..<localIndex {
|
||||
zIndex += childrenTotalCount(group.contents[i])
|
||||
}
|
||||
}
|
||||
|
||||
zIndex += 1
|
||||
|
||||
currentNode = parent!
|
||||
parent = nodesMap.parents(parent!).first
|
||||
}
|
||||
|
||||
if useCache {
|
||||
indexCache[node] = zIndex
|
||||
}
|
||||
|
||||
return zIndex
|
||||
}
|
||||
|
||||
class func animatedNodes(root: Node, animationCache: AnimationCache) -> [Node] {
|
||||
if animationCache.isAnimating(root) {
|
||||
return [root]
|
||||
}
|
||||
|
||||
guard let rootGroup = root as? Group else {
|
||||
return []
|
||||
}
|
||||
|
||||
var result = [Node]()
|
||||
rootGroup.contents.forEach { child in
|
||||
let childAnimatedNodes = animatedNodes(root: child, animationCache: animationCache)
|
||||
result.append(contentsOf: childAnimatedNodes)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@
|
||||
//
|
||||
|
||||
public enum Easing {
|
||||
case ease
|
||||
case linear
|
||||
case easeIn
|
||||
case easeOut
|
||||
case easeInOut
|
||||
case ease
|
||||
case linear
|
||||
case easeIn
|
||||
case easeOut
|
||||
case easeInOut
|
||||
}
|
||||
|
@ -1,40 +1,42 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public extension Rect {
|
||||
|
||||
convenience init(cgRect: CGRect) {
|
||||
self.init(
|
||||
x: Double(cgRect.origin.x),
|
||||
y: Double(cgRect.origin.y),
|
||||
w: Double(cgRect.size.width),
|
||||
h: Double(cgRect.size.height))
|
||||
}
|
||||
|
||||
func cgRect() -> CGRect {
|
||||
return CGRect(x: self.x, y: self.y, width: self.w, height: self.h)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) -> Rect {
|
||||
|
||||
// TODO: Rewrite using math
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
return Rect(cgRect: self.cgRect().applying(cgTransform))
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y) w:\(self.w) h:\(self.h)"
|
||||
}
|
||||
|
||||
convenience init(cgRect: CGRect) {
|
||||
self.init(
|
||||
x: Double(cgRect.origin.x),
|
||||
y: Double(cgRect.origin.y),
|
||||
w: Double(cgRect.size.width),
|
||||
h: Double(cgRect.size.height))
|
||||
}
|
||||
|
||||
func cgRect() -> CGRect {
|
||||
return CGRect(x: self.x, y: self.y, width: self.w, height: self.h)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) -> Rect {
|
||||
|
||||
// TODO: Rewrite using math
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
return Rect(cgRect: self.cgRect().applying(cgTransform))
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y) w:\(self.w) h:\(self.h)"
|
||||
}
|
||||
}
|
||||
|
||||
public extension Point {
|
||||
public func cgPoint() -> CGPoint {
|
||||
return CGPoint(x: self.x, y: self.y)
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y)"
|
||||
}
|
||||
public func cgPoint() -> CGPoint {
|
||||
return CGPoint(x: self.x, y: self.y)
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y)"
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,24 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public func >> (a: Double, b: Double) -> OpacityAnimationDescription {
|
||||
return OpacityAnimationDescription(valueFunc: { t in
|
||||
return a.interpolate(b, progress: t)
|
||||
})
|
||||
return OpacityAnimationDescription(valueFunc: { t in
|
||||
return a.interpolate(b, progress: t)
|
||||
})
|
||||
}
|
||||
|
||||
public func >> (a: Transform, b: Transform) -> TransformAnimationDescription {
|
||||
return TransformAnimationDescription(valueFunc: { t in
|
||||
return a.interpolate(b, progress: t)
|
||||
})
|
||||
return TransformAnimationDescription(valueFunc: { t in
|
||||
return a.interpolate(b, progress: t)
|
||||
})
|
||||
}
|
||||
|
||||
public func >> (a: Locus, b: Locus) -> MorphingAnimationDescription {
|
||||
return MorphingAnimationDescription(valueFunc: { t in
|
||||
// return a.interpolate(b, progress: t)
|
||||
if t == 0.0 {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
})
|
||||
return MorphingAnimationDescription(valueFunc: { t in
|
||||
// return a.interpolate(b, progress: t)
|
||||
if t == 0.0 {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
})
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
public protocol ContentsInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Array: ContentsInterpolation {
|
||||
public func interpolate(_ endValue: Array, progress: Double) -> Array {
|
||||
return self
|
||||
}
|
||||
public func interpolate(_ endValue: Array, progress: Double) -> Array {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
public protocol DoubleInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Double: DoubleInterpolation {
|
||||
public func interpolate(_ endValue: Double, progress: Double) -> Double {
|
||||
return self + (endValue - self) * progress
|
||||
}
|
||||
|
||||
public func interpolate(_ endValue: Double, progress: Double) -> Double {
|
||||
return self + (endValue - self) * progress
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
public protocol LocusInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Locus: LocusInterpolation {
|
||||
public func interpolate(_ endValue: Locus, progress: Double) -> Self {
|
||||
|
||||
return self
|
||||
}
|
||||
public func interpolate(_ endValue: Locus, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@
|
||||
//
|
||||
|
||||
public protocol ShapeInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Shape: ShapeInterpolation {
|
||||
public func interpolate(_ endValue: Shape, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
public func interpolate(_ endValue: Shape, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
|
||||
public protocol TransformInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Transform: TransformInterpolation {
|
||||
public func interpolate(_ endValue: Transform, progress: Double) -> Transform {
|
||||
return Transform(m11: self.m11.interpolate(endValue.m11, progress: progress),
|
||||
m12: self.m12.interpolate(endValue.m12, progress: progress),
|
||||
m21: self.m21.interpolate(endValue.m21, progress: progress),
|
||||
m22: self.m22.interpolate(endValue.m22, progress: progress),
|
||||
dx: self.dx.interpolate(endValue.dx, progress: progress),
|
||||
dy: self.dy.interpolate(endValue.dy, progress: progress))
|
||||
}
|
||||
public func interpolate(_ endValue: Transform, progress: Double) -> Transform {
|
||||
return Transform(m11: self.m11.interpolate(endValue.m11, progress: progress),
|
||||
m12: self.m12.interpolate(endValue.m12, progress: progress),
|
||||
m21: self.m21.interpolate(endValue.m21, progress: progress),
|
||||
m22: self.m22.interpolate(endValue.m22, progress: progress),
|
||||
dx: self.dx.interpolate(endValue.dx, progress: progress),
|
||||
dy: self.dy.interpolate(endValue.dy, progress: progress))
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,31 @@
|
||||
|
||||
|
||||
func boundsForFunc(_ func2d: func2D) -> Rect {
|
||||
|
||||
var p = func2d(0.0)
|
||||
var minX = p.x
|
||||
var minY = p.y
|
||||
var maxX = minX
|
||||
var maxY = minY
|
||||
|
||||
for t in stride(from: 0.0, to: 1.0, by: 0.01) {
|
||||
p = func2d(t)
|
||||
|
||||
if minX > p.x {
|
||||
minX = p.x
|
||||
}
|
||||
|
||||
if minY > p.y {
|
||||
minY = p.y
|
||||
}
|
||||
|
||||
if maxX < p.x {
|
||||
maxX = p.x
|
||||
}
|
||||
|
||||
if maxY < p.y {
|
||||
maxY = p.y
|
||||
}
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY, w: maxX - minX, h: maxY - minY)
|
||||
|
||||
var p = func2d(0.0)
|
||||
var minX = p.x
|
||||
var minY = p.y
|
||||
var maxX = minX
|
||||
var maxY = minY
|
||||
|
||||
for t in stride(from: 0.0, to: 1.0, by: 0.01) {
|
||||
p = func2d(t)
|
||||
|
||||
if minX > p.x {
|
||||
minX = p.x
|
||||
}
|
||||
|
||||
if minY > p.y {
|
||||
minY = p.y
|
||||
}
|
||||
|
||||
if maxX < p.x {
|
||||
maxX = p.x
|
||||
}
|
||||
|
||||
if maxY < p.y {
|
||||
maxY = p.y
|
||||
}
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY, w: maxX - minX, h: maxY - minY)
|
||||
}
|
||||
|
@ -1,107 +1,107 @@
|
||||
|
||||
func pathBounds(_ path: Path) -> Rect? {
|
||||
|
||||
guard let firstSegment = path.segments.first else {
|
||||
return .none
|
||||
}
|
||||
|
||||
let firstSegmentInfo = pathSegmenInfo(firstSegment, currentPoint: .none, currentBezierPoint: .none)
|
||||
var bounds = firstSegmentInfo.0
|
||||
var currentPoint = firstSegmentInfo.1 ?? Point.origin
|
||||
var cubicBezierPoint: Point?
|
||||
|
||||
for segment in path.segments {
|
||||
let segmentInfo = pathSegmenInfo(segment, currentPoint: currentPoint, currentBezierPoint: cubicBezierPoint)
|
||||
if let segmentBounds = segmentInfo.0 {
|
||||
if segment.isAbsolute() {
|
||||
bounds = bounds?.union(rect: segmentBounds)
|
||||
} else {
|
||||
bounds = bounds?.union(rect: segmentBounds.move(offset: currentPoint))
|
||||
}
|
||||
}
|
||||
|
||||
if let segmentLastPoint = segmentInfo.1 {
|
||||
if segment.isAbsolute() {
|
||||
currentPoint = segmentLastPoint
|
||||
} else {
|
||||
currentPoint = segmentLastPoint.add(currentPoint)
|
||||
}
|
||||
}
|
||||
|
||||
if let segmentBezierPoint = segmentInfo.2 {
|
||||
if segment.isAbsolute() {
|
||||
cubicBezierPoint = segmentBezierPoint
|
||||
} else {
|
||||
cubicBezierPoint = segmentBezierPoint.add(currentPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bounds
|
||||
|
||||
guard let firstSegment = path.segments.first else {
|
||||
return .none
|
||||
}
|
||||
|
||||
let firstSegmentInfo = pathSegmenInfo(firstSegment, currentPoint: .none, currentBezierPoint: .none)
|
||||
var bounds = firstSegmentInfo.0
|
||||
var currentPoint = firstSegmentInfo.1 ?? Point.origin
|
||||
var cubicBezierPoint: Point?
|
||||
|
||||
for segment in path.segments {
|
||||
let segmentInfo = pathSegmenInfo(segment, currentPoint: currentPoint, currentBezierPoint: cubicBezierPoint)
|
||||
if let segmentBounds = segmentInfo.0 {
|
||||
if segment.isAbsolute() {
|
||||
bounds = bounds?.union(rect: segmentBounds)
|
||||
} else {
|
||||
bounds = bounds?.union(rect: segmentBounds.move(offset: currentPoint))
|
||||
}
|
||||
}
|
||||
|
||||
if let segmentLastPoint = segmentInfo.1 {
|
||||
if segment.isAbsolute() {
|
||||
currentPoint = segmentLastPoint
|
||||
} else {
|
||||
currentPoint = segmentLastPoint.add(currentPoint)
|
||||
}
|
||||
}
|
||||
|
||||
if let segmentBezierPoint = segmentInfo.2 {
|
||||
if segment.isAbsolute() {
|
||||
cubicBezierPoint = segmentBezierPoint
|
||||
} else {
|
||||
cubicBezierPoint = segmentBezierPoint.add(currentPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bounds
|
||||
}
|
||||
|
||||
func pathSegmenInfo(_ segment: PathSegment, currentPoint: Point?, currentBezierPoint: Point?)
|
||||
-> (Rect?, Point?, Point?) { // Bounds, last point, last bezier point TODO: Replace as struct
|
||||
|
||||
let data = segment.data
|
||||
switch segment.type {
|
||||
case .m, .M:
|
||||
let point = Point(x: data[0], y: data[1])
|
||||
return (Rect(x: point.x, y: point.y, w: 0.0, h: 0.0), point, .none)
|
||||
case .c, .C:
|
||||
return (cubicBounds(data), Point(x: data[4], y: data[5]), Point(x: data[2], y: data[3]))
|
||||
case .s, .S:
|
||||
guard let currentPoint = currentPoint else {
|
||||
return (.none, .none, .none)
|
||||
}
|
||||
|
||||
var p2 = currentPoint
|
||||
if let bezierPoint = currentBezierPoint {
|
||||
p2 = Point(
|
||||
x: 2.0 * currentPoint.x - bezierPoint.x,
|
||||
y: 2.0 * currentPoint.y - bezierPoint.y)
|
||||
}
|
||||
|
||||
return (sCubicBounds(data, currentPoint: currentPoint, currentBezierPoint: currentBezierPoint),
|
||||
Point(x: data[2], y: data[3]),
|
||||
Point(x: p2.x, y: p2.y))
|
||||
case .h, .H:
|
||||
return (Rect(x: 0.0, y: 0.0, w: data[0], h: 0.0), Point(x: data[0], y: 0.0), .none)
|
||||
case .v, .V:
|
||||
return (Rect(x: 0.0, y: 0.0, w: 0.0, h: data[0]), Point(x: 0.0, y: data[0]), .none)
|
||||
case .l, .L:
|
||||
return (Rect(x: data[0], y: data[1], w: 0.0, h: 0.0), Point(x: data[0], y: data[1]), .none)
|
||||
default:
|
||||
return (.none, .none, .none)
|
||||
}
|
||||
-> (Rect?, Point?, Point?) { // Bounds, last point, last bezier point TODO: Replace as struct
|
||||
|
||||
let data = segment.data
|
||||
switch segment.type {
|
||||
case .m, .M:
|
||||
let point = Point(x: data[0], y: data[1])
|
||||
return (Rect(x: point.x, y: point.y, w: 0.0, h: 0.0), point, .none)
|
||||
case .c, .C:
|
||||
return (cubicBounds(data), Point(x: data[4], y: data[5]), Point(x: data[2], y: data[3]))
|
||||
case .s, .S:
|
||||
guard let currentPoint = currentPoint else {
|
||||
return (.none, .none, .none)
|
||||
}
|
||||
|
||||
var p2 = currentPoint
|
||||
if let bezierPoint = currentBezierPoint {
|
||||
p2 = Point(
|
||||
x: 2.0 * currentPoint.x - bezierPoint.x,
|
||||
y: 2.0 * currentPoint.y - bezierPoint.y)
|
||||
}
|
||||
|
||||
return (sCubicBounds(data, currentPoint: currentPoint, currentBezierPoint: currentBezierPoint),
|
||||
Point(x: data[2], y: data[3]),
|
||||
Point(x: p2.x, y: p2.y))
|
||||
case .h, .H:
|
||||
return (Rect(x: 0.0, y: 0.0, w: data[0], h: 0.0), Point(x: data[0], y: 0.0), .none)
|
||||
case .v, .V:
|
||||
return (Rect(x: 0.0, y: 0.0, w: 0.0, h: data[0]), Point(x: 0.0, y: data[0]), .none)
|
||||
case .l, .L:
|
||||
return (Rect(x: data[0], y: data[1], w: 0.0, h: 0.0), Point(x: data[0], y: data[1]), .none)
|
||||
default:
|
||||
return (.none, .none, .none)
|
||||
}
|
||||
}
|
||||
|
||||
private func cubicBounds(_ data: [Double]) -> Rect {
|
||||
let p0 = Point(x: 0, y: 0)
|
||||
let p1 = Point(x: data[0], y: data[1])
|
||||
let p2 = Point(x: data[2], y: data[3])
|
||||
let p3 = Point(x: data[4], y: data[5])
|
||||
|
||||
let bezier3 = { (t: Double) -> Point in return BezierFunc2D(t, p0: p0, p1: p1, p2: p2, p3: p3) }
|
||||
|
||||
// TODO: Replace with accurate implementation via derivative
|
||||
return boundsForFunc(bezier3)
|
||||
let p0 = Point(x: 0, y: 0)
|
||||
let p1 = Point(x: data[0], y: data[1])
|
||||
let p2 = Point(x: data[2], y: data[3])
|
||||
let p3 = Point(x: data[4], y: data[5])
|
||||
|
||||
let bezier3 = { (t: Double) -> Point in return BezierFunc2D(t, p0: p0, p1: p1, p2: p2, p3: p3) }
|
||||
|
||||
// TODO: Replace with accurate implementation via derivative
|
||||
return boundsForFunc(bezier3)
|
||||
}
|
||||
|
||||
private func sCubicBounds(_ data: [Double], currentPoint: Point, currentBezierPoint: Point?) -> Rect {
|
||||
|
||||
let p0 = Point(x: 0, y: 0)
|
||||
let p1 = Point(x: data[0], y: data[1])
|
||||
let p3 = Point(x: data[2], y: data[3])
|
||||
var p2 = currentPoint
|
||||
if let bezierPoint = currentBezierPoint {
|
||||
p2 = Point(
|
||||
x: 2.0 * currentPoint.x - bezierPoint.x,
|
||||
y: 2.0 * currentPoint.y - bezierPoint.y)
|
||||
}
|
||||
|
||||
let bezier3 = { (t: Double) -> Point in return BezierFunc2D(t, p0: p0, p1: p1, p2: p2, p3: p3) }
|
||||
|
||||
// TODO: Replace with accurate implementation via derivative
|
||||
return boundsForFunc(bezier3)
|
||||
|
||||
let p0 = Point(x: 0, y: 0)
|
||||
let p1 = Point(x: data[0], y: data[1])
|
||||
let p3 = Point(x: data[2], y: data[3])
|
||||
var p2 = currentPoint
|
||||
if let bezierPoint = currentBezierPoint {
|
||||
p2 = Point(
|
||||
x: 2.0 * currentPoint.x - bezierPoint.x,
|
||||
y: 2.0 * currentPoint.y - bezierPoint.y)
|
||||
}
|
||||
|
||||
let bezier3 = { (t: Double) -> Point in return BezierFunc2D(t, p0: p0, p1: p1, p2: p2, p3: p3) }
|
||||
|
||||
// TODO: Replace with accurate implementation via derivative
|
||||
return boundsForFunc(bezier3)
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ import Foundation
|
||||
typealias func2D = ((_ t: Double) -> (Point))
|
||||
|
||||
func BezierFunc2D(_ t: Double, p0: Point, p1: Point, p2: Point, p3: Point) -> Point {
|
||||
return Point(
|
||||
x: polynom3(t, p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x),
|
||||
y: polynom3(t, p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y))
|
||||
return Point(
|
||||
x: polynom3(t, p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x),
|
||||
y: polynom3(t, p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y))
|
||||
}
|
||||
|
||||
func polynom3(_ t: Double, p0: Double, p1: Double, p2: Double, p3: Double) -> Double {
|
||||
let t1 = 1.0 - t
|
||||
return pow(t1, 3.0) * p0 + 3.0 * t * pow(t1, 2.0) * p1 + 3.0 * t * t * t1 * p2 + pow(t, 3.0) * p3
|
||||
let t1 = 1.0 - t
|
||||
return pow(t1, 3.0) * p0 + 3.0 * t * pow(t1, 2.0) * p1 + 3.0 * t * t * t1 * p2 + pow(t, 3.0) * p3
|
||||
}
|
||||
|
@ -1,94 +1,93 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
internal class AnimationSequence: BasicAnimation {
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0) {
|
||||
self.animations = animations
|
||||
|
||||
super.init()
|
||||
|
||||
self.type = .sequence
|
||||
self.node = animations.first?.node
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override func getDuration() -> Double {
|
||||
let originalDuration = animations.map({ $0.getDuration() }).reduce(0, { $0 + $1 })
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0) {
|
||||
self.animations = animations
|
||||
|
||||
if autoreverses {
|
||||
return originalDuration * 2.0
|
||||
}
|
||||
|
||||
return originalDuration
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
super.stop()
|
||||
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
return
|
||||
}
|
||||
|
||||
active.stop()
|
||||
}
|
||||
super.init()
|
||||
|
||||
open override func pause() {
|
||||
super.pause()
|
||||
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
return
|
||||
}
|
||||
|
||||
active.pause()
|
||||
self.type = .sequence
|
||||
self.node = animations.first?.node
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override func getDuration() -> Double {
|
||||
let originalDuration = animations.map({ $0.getDuration() }).reduce(0, { $0 + $1 })
|
||||
|
||||
if autoreverses {
|
||||
return originalDuration * 2.0
|
||||
}
|
||||
|
||||
open override func play() {
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
super.play()
|
||||
return
|
||||
}
|
||||
|
||||
manualStop = false
|
||||
paused = false
|
||||
|
||||
active.play()
|
||||
return originalDuration
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
super.stop()
|
||||
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
return
|
||||
}
|
||||
|
||||
open override func state() -> AnimationState {
|
||||
for animation in animations {
|
||||
let state = animation.state()
|
||||
if state != .initial {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
return .initial
|
||||
active.stop()
|
||||
}
|
||||
|
||||
open override func pause() {
|
||||
super.pause()
|
||||
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
return
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
var reversedAnimations = [BasicAnimation]()
|
||||
animations.forEach { animation in
|
||||
reversedAnimations.append(animation.reverse() as! BasicAnimation)
|
||||
}
|
||||
|
||||
let reversedSequence = reversedAnimations.reversed().sequence(delay: self.delay) as! BasicAnimation
|
||||
reversedSequence.completion = completion
|
||||
reversedSequence.progress = progress
|
||||
|
||||
return reversedSequence
|
||||
}
|
||||
|
||||
active.pause()
|
||||
}
|
||||
|
||||
open override func play() {
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
super.play()
|
||||
return
|
||||
}
|
||||
|
||||
manualStop = false
|
||||
paused = false
|
||||
|
||||
active.play()
|
||||
}
|
||||
|
||||
open override func state() -> AnimationState {
|
||||
for animation in animations {
|
||||
let state = animation.state()
|
||||
if state != .initial {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
return .initial
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
var reversedAnimations = [BasicAnimation]()
|
||||
animations.forEach { animation in
|
||||
reversedAnimations.append(animation.reverse() as! BasicAnimation)
|
||||
}
|
||||
|
||||
let reversedSequence = reversedAnimations.reversed().sequence(delay: self.delay) as! BasicAnimation
|
||||
reversedSequence.completion = completion
|
||||
reversedSequence.progress = progress
|
||||
|
||||
return reversedSequence
|
||||
}
|
||||
}
|
||||
|
||||
public extension Sequence where Iterator.Element: Animation {
|
||||
public func sequence(delay: Double = 0.0) -> Animation {
|
||||
|
||||
var sequence = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
sequence.append(animation as! BasicAnimation)
|
||||
}
|
||||
return AnimationSequence(animations: sequence, delay: delay)
|
||||
}
|
||||
public func sequence(delay: Double = 0.0) -> Animation {
|
||||
|
||||
var sequence = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
sequence.append(animation as! BasicAnimation)
|
||||
}
|
||||
return AnimationSequence(animations: sequence, delay: delay)
|
||||
}
|
||||
}
|
||||
|
@ -1,90 +1,89 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
internal class CombineAnimation: BasicAnimation {
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0, node: Node? = .none) {
|
||||
self.animations = animations
|
||||
|
||||
super.init()
|
||||
|
||||
self.type = .combine
|
||||
self.node = node ?? animations.first?.node
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override func getDuration() -> Double {
|
||||
if let maxElement = animations.map({ $0.getDuration() }).max() {
|
||||
return maxElement
|
||||
}
|
||||
|
||||
return 0.0
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
var reversedAnimations = [BasicAnimation]()
|
||||
animations.forEach { animation in
|
||||
reversedAnimations.append(animation.reverse() as! BasicAnimation)
|
||||
}
|
||||
|
||||
let combineReversed = reversedAnimations.combine(delay: self.delay) as! BasicAnimation
|
||||
combineReversed.completion = completion
|
||||
combineReversed.progress = progress
|
||||
|
||||
return combineReversed
|
||||
}
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0, node: Node? = .none) {
|
||||
self.animations = animations
|
||||
|
||||
open override func play() {
|
||||
animations.forEach { animation in
|
||||
animation.paused = false
|
||||
animation.manualStop = false
|
||||
}
|
||||
|
||||
super.play()
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
super.stop()
|
||||
|
||||
animations.forEach { animation in
|
||||
animation.stop()
|
||||
}
|
||||
}
|
||||
super.init()
|
||||
|
||||
open override func pause() {
|
||||
super.pause()
|
||||
|
||||
animations.forEach { animation in
|
||||
animation.pause()
|
||||
}
|
||||
self.type = .combine
|
||||
self.node = node ?? animations.first?.node
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override func getDuration() -> Double {
|
||||
if let maxElement = animations.map({ $0.getDuration() }).max() {
|
||||
return maxElement
|
||||
}
|
||||
|
||||
open override func state() -> AnimationState {
|
||||
var result = AnimationState.initial
|
||||
for animation in animations {
|
||||
let state = animation.state()
|
||||
if state == .running {
|
||||
return .running
|
||||
}
|
||||
|
||||
if state != .initial {
|
||||
result = state
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return 0.0
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
var reversedAnimations = [BasicAnimation]()
|
||||
animations.forEach { animation in
|
||||
reversedAnimations.append(animation.reverse() as! BasicAnimation)
|
||||
}
|
||||
|
||||
let combineReversed = reversedAnimations.combine(delay: self.delay) as! BasicAnimation
|
||||
combineReversed.completion = completion
|
||||
combineReversed.progress = progress
|
||||
|
||||
return combineReversed
|
||||
}
|
||||
|
||||
open override func play() {
|
||||
animations.forEach { animation in
|
||||
animation.paused = false
|
||||
animation.manualStop = false
|
||||
}
|
||||
|
||||
super.play()
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
super.stop()
|
||||
|
||||
animations.forEach { animation in
|
||||
animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
open override func pause() {
|
||||
super.pause()
|
||||
|
||||
animations.forEach { animation in
|
||||
animation.pause()
|
||||
}
|
||||
}
|
||||
|
||||
open override func state() -> AnimationState {
|
||||
var result = AnimationState.initial
|
||||
for animation in animations {
|
||||
let state = animation.state()
|
||||
if state == .running {
|
||||
return .running
|
||||
}
|
||||
|
||||
if state != .initial {
|
||||
result = state
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
public extension Sequence where Iterator.Element: Animation {
|
||||
public func combine(delay: Double = 0.0, node: Node? = .none) -> Animation {
|
||||
|
||||
var toCombine = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
toCombine.append(animation as! BasicAnimation)
|
||||
}
|
||||
return CombineAnimation(animations: toCombine, delay: delay, node: node)
|
||||
}
|
||||
public func combine(delay: Double = 0.0, node: Node? = .none) -> Animation {
|
||||
|
||||
var toCombine = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
toCombine.append(animation as! BasicAnimation)
|
||||
}
|
||||
return CombineAnimation(animations: toCombine, delay: delay, node: node)
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +1,64 @@
|
||||
|
||||
internal class ContentsAnimation: AnimationImpl<[Node]> {
|
||||
|
||||
init(animatedGroup: Group, valueFunc: @escaping (Double) -> [Node], animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
init(animatedGroup: Group, valueFunc: @escaping (Double) -> [Node], animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
super.init(observableValue: animatedGroup.contentsVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .contents
|
||||
node = animatedGroup
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
super.init(observableValue: animatedGroup.contentsVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .contents
|
||||
node = animatedGroup
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedGroup: Group, factory: @escaping (() -> ((Double) -> [Node])), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedGroup.contentsVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .contents
|
||||
node = animatedGroup
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
let factory = { () -> (Double) -> [Node] in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> [Node] in
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedGroup: Group, factory: @escaping (() -> ((Double) -> [Node])), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedGroup.contentsVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .contents
|
||||
node = animatedGroup
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = ContentsAnimation(animatedGroup: node as! Group,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
let factory = { () -> (Double) -> [Node] in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> [Node] in
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = ContentsAnimation(animatedGroup: node as! Group,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
return reversedAnimation
|
||||
}
|
||||
}
|
||||
|
||||
public extension AnimatableVariable where T: ContentsInterpolation {
|
||||
|
||||
public func animation(_ f: @escaping (Double) -> [Node]) -> Animation {
|
||||
let group = node! as! Group
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: false)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let group = node! as! Group
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(_ f: @escaping (Double) -> [Node]) {
|
||||
let group = node! as! Group
|
||||
let _ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: true)
|
||||
}
|
||||
|
||||
public func animate(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) {
|
||||
let group = node! as! Group
|
||||
let _ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping (Double) -> [Node]) -> Animation {
|
||||
let group = node! as! Group
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: false)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let group = node! as! Group
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(_ f: @escaping (Double) -> [Node]) {
|
||||
let group = node! as! Group
|
||||
let _ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: true)
|
||||
}
|
||||
|
||||
public func animate(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) {
|
||||
let group = node! as! Group
|
||||
let _ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
}
|
||||
|
@ -1,176 +1,176 @@
|
||||
|
||||
class MorphingAnimation: AnimationImpl<Locus> {
|
||||
class MorphingAnimation: AnimationImpl<Locus> {
|
||||
|
||||
convenience init(animatedNode: Shape, startValue: Locus, finalValue: Locus, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
convenience init(animatedNode: Shape, startValue: Locus, finalValue: Locus, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Locus in
|
||||
return finalValue
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
let interpolationFunc = { (t: Double) -> Locus in
|
||||
return finalValue
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Locus, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.formVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .morphing
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Locus, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.formVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .morphing
|
||||
node = animatedNode
|
||||
|
||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Locus)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.formVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .morphing
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Locus)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.formVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .morphing
|
||||
node = animatedNode
|
||||
|
||||
// Pause state not available for discreet animation
|
||||
override public func pause() {
|
||||
stop()
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
// Pause state not available for discreet animation
|
||||
override public func pause() {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
public typealias MorphingAnimationDescription = AnimationDescription<Locus>
|
||||
|
||||
public extension AnimatableVariable where T: LocusInterpolation {
|
||||
public func animate(_ desc: MorphingAnimationDescription) {
|
||||
let _ = MorphingAnimation(animatedNode: node as! Shape, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
public func animate(_ desc: MorphingAnimationDescription) {
|
||||
let _ = MorphingAnimation(animatedNode: node as! Shape, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation(_ desc: MorphingAnimationDescription) -> Animation {
|
||||
return MorphingAnimation(animatedNode: node as! Shape, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(from: Locus? =
|
||||
nil, to: Locus, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? (node as! Shape).form) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animation(from: Locus? = nil, to: Locus, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animation(_ desc: MorphingAnimationDescription) -> Animation {
|
||||
return MorphingAnimation(animatedNode: node as! Shape, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(from: Locus? =
|
||||
nil, to: Locus, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? (node as! Shape).form) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animation(from: Locus? = nil, to: Locus, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
let origin = (node as! Shape).form
|
||||
let factory = { () -> (Double) -> Locus in
|
||||
return { (t: Double) in
|
||||
if t == 0.0 {
|
||||
return origin
|
||||
}
|
||||
|
||||
let origin = (node as! Shape).form
|
||||
let factory = { () -> (Double) -> Locus in
|
||||
return { (t: Double) in
|
||||
if t == 0.0 {
|
||||
return origin
|
||||
}
|
||||
|
||||
return to }
|
||||
}
|
||||
|
||||
return MorphingAnimation(animatedNode: self.node as! Shape, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping (Double) -> Locus, during: Double, delay: Double = 0.0) -> Animation {
|
||||
return MorphingAnimation(animatedNode: node as! Shape, valueFunc: f, animationDuration: during, delay: delay)
|
||||
return to }
|
||||
}
|
||||
|
||||
return MorphingAnimation(animatedNode: self.node as! Shape, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping (Double) -> Locus, during: Double, delay: Double = 0.0) -> Animation {
|
||||
return MorphingAnimation(animatedNode: node as! Shape, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Group
|
||||
|
||||
public extension AnimatableVariable where T: ContentsInterpolation {
|
||||
public func animation(from: Group? = nil, to: [Node], during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
var fromNode = node as! Group
|
||||
if let passedFromNode = from {
|
||||
fromNode = passedFromNode
|
||||
}
|
||||
|
||||
// Shapes on same hierarhy level
|
||||
let fromShapes = fromNode.contents.flatMap{$0 as? Shape}
|
||||
let toShapes = to.flatMap{$0 as? Shape}
|
||||
let minPathsNumber = min(fromShapes.count, toShapes.count)
|
||||
|
||||
var animations = [Animation]()
|
||||
for i in 0..<minPathsNumber {
|
||||
let fromShape = fromShapes[i]
|
||||
let toShape = toShapes[i]
|
||||
|
||||
let animation = ShapeAnimation(animatedNode: fromShape, finalValue: toShape, animationDuration: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
if fromShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<fromShapes.count {
|
||||
let shapeToHide = fromShapes[i]
|
||||
let animation = shapeToHide.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
if toShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<toShapes.count {
|
||||
let shapeToShow = toShapes[i]
|
||||
shapeToShow.opacity = 0.0
|
||||
fromNode.contents.append(shapeToShow)
|
||||
|
||||
let animation = shapeToShow.opacityVar.animation(to: 1.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// Groups on same hierahy level
|
||||
let fromGroups = fromNode.contents.flatMap{$0 as? Group}
|
||||
let toGroups = to.flatMap{$0 as? Group}
|
||||
let minGroupsNumber = min(fromGroups.count, toGroups.count)
|
||||
for i in 0..<minGroupsNumber {
|
||||
let fromGroup = fromGroups[i]
|
||||
let toGroup = toGroups[i]
|
||||
let groupAnimation = fromGroup.contentsVar.animation(to: toGroup.contents, during: during, delay: delay)
|
||||
animations.append(groupAnimation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<fromGroups.count {
|
||||
let groupToHide = fromGroups[i]
|
||||
let animation = groupToHide.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<toGroups.count {
|
||||
let groupToShow = toGroups[i]
|
||||
groupToShow.opacity = 0.0
|
||||
fromNode.contents.append(groupToShow)
|
||||
|
||||
let animation = groupToShow.opacityVar.animation(to: 1.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
|
||||
// Rest nodes
|
||||
let fromNodes = fromNode.contents.filter {
|
||||
return !($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
let toNodes = to.filter {
|
||||
return !($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
fromNodes.forEach { node in
|
||||
let animation = node.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
toNodes.forEach { node in
|
||||
node.opacity = 0.0
|
||||
fromNode.contents.append(node)
|
||||
|
||||
let animation = node.opacityVar.animation(to: 1.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
return animations.combine(node: fromNode)
|
||||
public func animation(from: Group? = nil, to: [Node], during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
var fromNode = node as! Group
|
||||
if let passedFromNode = from {
|
||||
fromNode = passedFromNode
|
||||
}
|
||||
|
||||
public func animate(from: Group? = nil, to: [Node], during: Double = 1.0, delay: Double = 0.0) {
|
||||
animation(from: from, to: to, during: during, delay: delay).play()
|
||||
// Shapes on same hierarhy level
|
||||
let fromShapes = fromNode.contents.flatMap{$0 as? Shape}
|
||||
let toShapes = to.flatMap{$0 as? Shape}
|
||||
let minPathsNumber = min(fromShapes.count, toShapes.count)
|
||||
|
||||
var animations = [Animation]()
|
||||
for i in 0..<minPathsNumber {
|
||||
let fromShape = fromShapes[i]
|
||||
let toShape = toShapes[i]
|
||||
|
||||
let animation = ShapeAnimation(animatedNode: fromShape, finalValue: toShape, animationDuration: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
if fromShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<fromShapes.count {
|
||||
let shapeToHide = fromShapes[i]
|
||||
let animation = shapeToHide.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
if toShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<toShapes.count {
|
||||
let shapeToShow = toShapes[i]
|
||||
shapeToShow.opacity = 0.0
|
||||
fromNode.contents.append(shapeToShow)
|
||||
|
||||
let animation = shapeToShow.opacityVar.animation(to: 1.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// Groups on same hierahy level
|
||||
let fromGroups = fromNode.contents.flatMap{$0 as? Group}
|
||||
let toGroups = to.flatMap{$0 as? Group}
|
||||
let minGroupsNumber = min(fromGroups.count, toGroups.count)
|
||||
for i in 0..<minGroupsNumber {
|
||||
let fromGroup = fromGroups[i]
|
||||
let toGroup = toGroups[i]
|
||||
let groupAnimation = fromGroup.contentsVar.animation(to: toGroup.contents, during: during, delay: delay)
|
||||
animations.append(groupAnimation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<fromGroups.count {
|
||||
let groupToHide = fromGroups[i]
|
||||
let animation = groupToHide.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<toGroups.count {
|
||||
let groupToShow = toGroups[i]
|
||||
groupToShow.opacity = 0.0
|
||||
fromNode.contents.append(groupToShow)
|
||||
|
||||
let animation = groupToShow.opacityVar.animation(to: 1.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
|
||||
// Rest nodes
|
||||
let fromNodes = fromNode.contents.filter {
|
||||
return !($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
let toNodes = to.filter {
|
||||
return !($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
fromNodes.forEach { node in
|
||||
let animation = node.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
toNodes.forEach { node in
|
||||
node.opacity = 0.0
|
||||
fromNode.contents.append(node)
|
||||
|
||||
let animation = node.opacityVar.animation(to: 1.0, during:during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
return animations.combine(node: fromNode)
|
||||
}
|
||||
|
||||
public func animate(from: Group? = nil, to: [Node], during: Double = 1.0, delay: Double = 0.0) {
|
||||
animation(from: from, to: to, during: during, delay: delay).play()
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +1,80 @@
|
||||
|
||||
internal class OpacityAnimation: AnimationImpl<Double> {
|
||||
|
||||
convenience init(animatedNode: Node, startValue: Double, finalValue: Double, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Double in
|
||||
return startValue.interpolate(finalValue, progress: t)
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Node, valueFunc: @escaping (Double) -> Double, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.opacityVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .opacity
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(animatedNode: Node, startValue: Double, finalValue: Double, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Double)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.opacityVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .opacity
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
let interpolationFunc = { (t: Double) -> Double in
|
||||
return startValue.interpolate(finalValue, progress: t)
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
let factory = { () -> (Double) -> Double in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Double in
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = OpacityAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Node, valueFunc: @escaping (Double) -> Double, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.opacityVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .opacity
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Double)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.opacityVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .opacity
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
let factory = { () -> (Double) -> Double in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Double in
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = OpacityAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
}
|
||||
|
||||
public typealias OpacityAnimationDescription = AnimationDescription<Double>
|
||||
|
||||
public extension AnimatableVariable where T: DoubleInterpolation {
|
||||
public func animate(_ desc: OpacityAnimationDescription) {
|
||||
let _ = OpacityAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation(_ desc: OpacityAnimationDescription) -> Animation {
|
||||
return OpacityAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? node!.opacity) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animation(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
let origin = node!.opacity
|
||||
let factory = { () -> (Double) -> Double in
|
||||
return { (t: Double) in return origin.interpolate(to, progress: t) }
|
||||
}
|
||||
return OpacityAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
public func animate(_ desc: OpacityAnimationDescription) {
|
||||
let _ = OpacityAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation(_ desc: OpacityAnimationDescription) -> Animation {
|
||||
return OpacityAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? node!.opacity) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animation(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping ((Double) -> Double), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
return OpacityAnimation(animatedNode: node!, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
let origin = node!.opacity
|
||||
let factory = { () -> (Double) -> Double in
|
||||
return { (t: Double) in return origin.interpolate(to, progress: t) }
|
||||
}
|
||||
return OpacityAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping ((Double) -> Double), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
return OpacityAnimation(animatedNode: node!, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,134 +7,134 @@
|
||||
//
|
||||
|
||||
class ShapeAnimation: AnimationImpl<Shape> {
|
||||
convenience init(animatedNode: Shape, finalValue: Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Shape in
|
||||
if t == 0 {
|
||||
return Shape(form: animatedNode.form,
|
||||
fill: animatedNode.fill,
|
||||
stroke: animatedNode.stroke,
|
||||
place: animatedNode.place,
|
||||
opaque: animatedNode.opaque,
|
||||
opacity: animatedNode.opacity,
|
||||
clip: animatedNode.clip,
|
||||
effect: animatedNode.effect,
|
||||
visible: animatedNode.visible,
|
||||
tag: animatedNode.tag)
|
||||
}
|
||||
|
||||
return finalValue
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
convenience init(animatedNode: Shape, finalValue: Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Shape in
|
||||
if t == 0 {
|
||||
return Shape(form: animatedNode.form,
|
||||
fill: animatedNode.fill,
|
||||
stroke: animatedNode.stroke,
|
||||
place: animatedNode.place,
|
||||
opaque: animatedNode.opaque,
|
||||
opacity: animatedNode.opacity,
|
||||
clip: animatedNode.clip,
|
||||
effect: animatedNode.effect,
|
||||
visible: animatedNode.visible,
|
||||
tag: animatedNode.tag)
|
||||
}
|
||||
|
||||
return finalValue
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .shape
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .shape
|
||||
node = animatedNode
|
||||
|
||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .shape
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .shape
|
||||
node = animatedNode
|
||||
|
||||
// Pause state not available for discreet animation
|
||||
override public func pause() {
|
||||
stop()
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
// Pause state not available for discreet animation
|
||||
override public func pause() {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
public extension AnimatableVariable {
|
||||
public func animate<T:Stroke>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeStroke = shape.stroke as? T {
|
||||
safeFrom = shapeStroke
|
||||
} else {
|
||||
safeFrom = Stroke(width: 1.0) as? T
|
||||
}
|
||||
}
|
||||
|
||||
shape.stroke = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.stroke = to
|
||||
|
||||
let _ = ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: true)
|
||||
public func animate<T:Stroke>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeStroke = shape.stroke as? T {
|
||||
safeFrom = shapeStroke
|
||||
} else {
|
||||
safeFrom = Stroke(width: 1.0) as? T
|
||||
}
|
||||
}
|
||||
|
||||
public func animation<T:Stroke>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeStroke = shape.stroke as? T {
|
||||
safeFrom = shapeStroke
|
||||
} else {
|
||||
safeFrom = Stroke(width: 1.0) as? T
|
||||
}
|
||||
}
|
||||
|
||||
shape.stroke = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.stroke = to
|
||||
|
||||
return ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: false)
|
||||
shape.stroke = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.stroke = to
|
||||
|
||||
let _ = ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation<T:Stroke>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeStroke = shape.stroke as? T {
|
||||
safeFrom = shapeStroke
|
||||
} else {
|
||||
safeFrom = Stroke(width: 1.0) as? T
|
||||
}
|
||||
}
|
||||
|
||||
shape.stroke = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.stroke = to
|
||||
|
||||
return ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: false)
|
||||
}
|
||||
}
|
||||
|
||||
public extension AnimatableVariable {
|
||||
public func animate<T:Fill>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeFill = shape.fill as? T {
|
||||
safeFrom = shapeFill
|
||||
} else {
|
||||
safeFrom = Color.clear as? T
|
||||
}
|
||||
}
|
||||
|
||||
shape.fill = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.fill = to
|
||||
|
||||
let _ = ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: true)
|
||||
public func animate<T:Fill>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeFill = shape.fill as? T {
|
||||
safeFrom = shapeFill
|
||||
} else {
|
||||
safeFrom = Color.clear as? T
|
||||
}
|
||||
}
|
||||
|
||||
public func animation<T:Fill>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeFill = shape.fill as? T {
|
||||
safeFrom = shapeFill
|
||||
} else {
|
||||
safeFrom = Color.clear as? T
|
||||
}
|
||||
}
|
||||
|
||||
shape.fill = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.fill = to
|
||||
|
||||
return ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: false)
|
||||
shape.fill = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.fill = to
|
||||
|
||||
let _ = ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation<T:Fill>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let shape = node as! Shape
|
||||
|
||||
var safeFrom = from
|
||||
if safeFrom == nil {
|
||||
if let shapeFill = shape.fill as? T {
|
||||
safeFrom = shapeFill
|
||||
} else {
|
||||
safeFrom = Color.clear as? T
|
||||
}
|
||||
}
|
||||
|
||||
shape.fill = safeFrom
|
||||
|
||||
let finalShape = SceneUtils.shapeCopy(from: shape)
|
||||
finalShape.fill = to
|
||||
|
||||
return ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: false)
|
||||
}
|
||||
}
|
||||
|
@ -1,119 +1,119 @@
|
||||
import Foundation
|
||||
|
||||
internal class TransformAnimation: AnimationImpl<Transform> {
|
||||
|
||||
convenience init(animatedNode: Node, startValue: Transform, finalValue: Transform, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Transform in
|
||||
return startValue.interpolate(finalValue, progress: t)
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Node, valueFunc: @escaping (Double) -> Transform, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.placeVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .affineTransformation
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(animatedNode: Node, startValue: Transform, finalValue: Transform, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Transform)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.placeVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .affineTransformation
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
let interpolationFunc = { (t: Double) -> Transform in
|
||||
return startValue.interpolate(finalValue, progress: t)
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Transform in
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = TransformAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Node, valueFunc: @escaping (Double) -> Transform, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.placeVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .affineTransformation
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Transform)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
super.init(observableValue: animatedNode.placeVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .affineTransformation
|
||||
node = animatedNode
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Transform in
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = TransformAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
}
|
||||
|
||||
public typealias TransformAnimationDescription = AnimationDescription<Transform>
|
||||
|
||||
public extension AnimatableVariable where T: TransformInterpolation {
|
||||
public func animate(_ desc: TransformAnimationDescription) {
|
||||
let _ = TransformAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation(_ desc: TransformAnimationDescription) -> Animation {
|
||||
return TransformAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? node!.place) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animate(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let animation = self.animation(angle: angle, x: x, y: y, during: during, delay: delay)
|
||||
animation.play()
|
||||
public func animate(_ desc: TransformAnimationDescription) {
|
||||
let _ = TransformAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
public func animation(_ desc: TransformAnimationDescription) -> Animation {
|
||||
return TransformAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
public func animate(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? node!.place) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
public func animate(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let animation = self.animation(angle: angle, x: x, y: y, during: during, delay: delay)
|
||||
animation.play()
|
||||
}
|
||||
|
||||
|
||||
public func animation(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
|
||||
public func animation(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
let origin = node!.place
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
return { (t: Double) in return origin.interpolate(to, progress: t) }
|
||||
}
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping ((Double) -> Transform), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
return TransformAnimation(animatedNode: node!, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let origin = node!.place
|
||||
let bounds = node!.bounds()!
|
||||
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
return { t in
|
||||
let asin = sin(angle * t); let acos = cos(angle * t)
|
||||
|
||||
let rotation = Transform(
|
||||
m11: acos, m12: -asin,
|
||||
m21: asin, m22: acos,
|
||||
dx: 0.0, dy: 0.0
|
||||
)
|
||||
|
||||
let move = Transform.move(
|
||||
dx: x ?? bounds.w / 2.0,
|
||||
dy: y ?? bounds.h / 2.0
|
||||
)
|
||||
|
||||
let t1 = GeomUtils.concat(t1: move, t2: rotation)
|
||||
let t2 = GeomUtils.concat(t1: t1, t2: move.invert()!)
|
||||
let result = GeomUtils.concat(t1: origin, t2: t2)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
let origin = node!.place
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
return { (t: Double) in return origin.interpolate(to, progress: t) }
|
||||
}
|
||||
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(_ f: @escaping ((Double) -> Transform), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
return TransformAnimation(animatedNode: node!, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
public func animation(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let origin = node!.place
|
||||
let bounds = node!.bounds()!
|
||||
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
return { t in
|
||||
let asin = sin(angle * t); let acos = cos(angle * t)
|
||||
|
||||
let rotation = Transform(
|
||||
m11: acos, m12: -asin,
|
||||
m21: asin, m22: acos,
|
||||
dx: 0.0, dy: 0.0
|
||||
)
|
||||
|
||||
let move = Transform.move(
|
||||
dx: x ?? bounds.w / 2.0,
|
||||
dy: y ?? bounds.h / 2.0
|
||||
)
|
||||
|
||||
let t1 = GeomUtils.concat(t1: move, t2: rotation)
|
||||
let t2 = GeomUtils.concat(t1: t1, t2: move.invert()!)
|
||||
let result = GeomUtils.concat(t1: origin, t2: t2)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,190 +1,194 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
class AnimationCache {
|
||||
|
||||
class CachedLayer {
|
||||
let layer: ShapeLayer
|
||||
let animation: Animation
|
||||
var linksCounter = 1
|
||||
|
||||
required init(layer: ShapeLayer, animation: Animation) {
|
||||
self.layer = layer
|
||||
self.animation = animation
|
||||
}
|
||||
}
|
||||
|
||||
let sceneLayer: CALayer
|
||||
var layerCache = [Node: CachedLayer]()
|
||||
|
||||
required init(sceneLayer: CALayer) {
|
||||
self.sceneLayer = sceneLayer
|
||||
}
|
||||
|
||||
func layerForNode(_ node: Node, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
|
||||
guard let cachedLayer = layerCache[node] else {
|
||||
let layer = ShapeLayer()
|
||||
layer.shouldRenderContent = shouldRenderContent
|
||||
layer.animationCache = self
|
||||
|
||||
// Use to debug animation layers
|
||||
// layer.backgroundColor = UIColor.green.cgColor
|
||||
// layer.borderWidth = 1.0
|
||||
// layer.borderColor = UIColor.blue.cgColor
|
||||
|
||||
let calculatedBounds = customBounds ?? node.bounds()
|
||||
if let shapeBounds = calculatedBounds {
|
||||
let cgRect = shapeBounds.cgRect()
|
||||
|
||||
let origFrame = CGRect(x: 0.0, y: 0.0,
|
||||
width: round(cgRect.width),
|
||||
height: round(cgRect.height))
|
||||
|
||||
layer.bounds = origFrame
|
||||
layer.anchorPoint = CGPoint(
|
||||
x: -1.0 * cgRect.origin.x / cgRect.width,
|
||||
y: -1.0 * cgRect.origin.y / cgRect.height
|
||||
)
|
||||
layer.zPosition = CGFloat(AnimationUtils.absoluteIndex(node))
|
||||
|
||||
layer.renderTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y)
|
||||
|
||||
let nodeTransform = RenderUtils.mapTransform(AnimationUtils.absolutePosition(node))
|
||||
layer.transform = CATransform3DMakeAffineTransform(nodeTransform)
|
||||
|
||||
// Clip
|
||||
if let clip = AnimationUtils.absoluteClip(node: node) {
|
||||
let maskLayer = CAShapeLayer()
|
||||
let origPath = RenderUtils.toBezierPath(clip).cgPath
|
||||
var offsetTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y)
|
||||
let clipPath = origPath.mutableCopy(using: &offsetTransform)
|
||||
maskLayer.path = clipPath
|
||||
layer.mask = maskLayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
layer.opacity = Float(node.opacity)
|
||||
layer.node = node
|
||||
|
||||
layer.contentsScale = calculateAnimationScale(animation: animation)
|
||||
|
||||
layer.setNeedsDisplay()
|
||||
sceneLayer.addSublayer(layer)
|
||||
|
||||
layerCache[node] = CachedLayer(layer: layer, animation: animation)
|
||||
sceneLayer.setNeedsDisplay()
|
||||
|
||||
return layer
|
||||
}
|
||||
|
||||
cachedLayer.linksCounter += 1
|
||||
|
||||
return cachedLayer.layer
|
||||
}
|
||||
|
||||
class CachedLayer {
|
||||
let layer: ShapeLayer
|
||||
let animation: Animation
|
||||
var linksCounter = 1
|
||||
|
||||
private func calculateAnimationScale(animation: Animation) -> CGFloat {
|
||||
let defaultScale = UIScreen.main.scale
|
||||
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return defaultScale
|
||||
}
|
||||
|
||||
let animFunc = transformAnimation.getVFunc()
|
||||
let origBounds = Rect(x: 0.0, y: 0.0, w: 1.0, h: 1.0)
|
||||
|
||||
let startTransform = animFunc(0.0)
|
||||
let startBounds = origBounds.applyTransform(startTransform)
|
||||
var startArea = startBounds.w * startBounds.h
|
||||
|
||||
// zero scale protection
|
||||
if startArea == 0.0 {
|
||||
startArea = 0.1
|
||||
}
|
||||
|
||||
var maxArea = startArea
|
||||
var t = 0.0
|
||||
let step = 0.1
|
||||
while t <= 1.0 {
|
||||
let currentTransform = animFunc(t)
|
||||
let currentBounds = origBounds.applyTransform(currentTransform)
|
||||
let currentArea = currentBounds.w * currentBounds.h
|
||||
if maxArea < currentArea {
|
||||
maxArea = currentArea
|
||||
}
|
||||
|
||||
t = t + step
|
||||
}
|
||||
|
||||
return defaultScale * CGFloat(sqrt(maxArea))
|
||||
required init(layer: ShapeLayer, animation: Animation) {
|
||||
self.layer = layer
|
||||
self.animation = animation
|
||||
}
|
||||
}
|
||||
|
||||
let sceneLayer: CALayer
|
||||
var layerCache = [Node: CachedLayer]()
|
||||
|
||||
required init(sceneLayer: CALayer) {
|
||||
self.sceneLayer = sceneLayer
|
||||
}
|
||||
|
||||
func layerForNode(_ node: Node, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
|
||||
guard let cachedLayer = layerCache[node] else {
|
||||
let layer = ShapeLayer()
|
||||
layer.shouldRenderContent = shouldRenderContent
|
||||
layer.animationCache = self
|
||||
|
||||
// Use to debug animation layers
|
||||
// layer.backgroundColor = MColor.green.cgColor
|
||||
// layer.borderWidth = 1.0
|
||||
// layer.borderColor = MColor.blue.cgColor
|
||||
|
||||
let calculatedBounds = customBounds ?? node.bounds()
|
||||
if let shapeBounds = calculatedBounds {
|
||||
let cgRect = shapeBounds.cgRect()
|
||||
|
||||
let origFrame = CGRect(x: 0.0, y: 0.0,
|
||||
width: round(cgRect.width),
|
||||
height: round(cgRect.height))
|
||||
|
||||
layer.bounds = origFrame
|
||||
layer.anchorPoint = CGPoint(
|
||||
x: -1.0 * cgRect.origin.x / cgRect.width,
|
||||
y: -1.0 * cgRect.origin.y / cgRect.height
|
||||
)
|
||||
layer.zPosition = CGFloat(AnimationUtils.absoluteIndex(node))
|
||||
|
||||
layer.renderTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y)
|
||||
|
||||
let nodeTransform = RenderUtils.mapTransform(AnimationUtils.absolutePosition(node))
|
||||
layer.transform = CATransform3DMakeAffineTransform(nodeTransform)
|
||||
|
||||
// Clip
|
||||
if let clip = AnimationUtils.absoluteClip(node: node) {
|
||||
let maskLayer = CAShapeLayer()
|
||||
let origPath = RenderUtils.toBezierPath(clip).cgPath
|
||||
var offsetTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y)
|
||||
let clipPath = origPath.mutableCopy(using: &offsetTransform)
|
||||
maskLayer.path = clipPath
|
||||
layer.mask = maskLayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
layer.opacity = Float(node.opacity)
|
||||
layer.node = node
|
||||
|
||||
layer.contentsScale = calculateAnimationScale(animation: animation)
|
||||
|
||||
layer.setNeedsDisplay()
|
||||
sceneLayer.addSublayer(layer)
|
||||
|
||||
layerCache[node] = CachedLayer(layer: layer, animation: animation)
|
||||
sceneLayer.setNeedsDisplay()
|
||||
|
||||
return layer
|
||||
}
|
||||
|
||||
func freeLayer(_ node: Node) {
|
||||
guard let cachedLayer = layerCache[node] else {
|
||||
return
|
||||
}
|
||||
|
||||
cachedLayer.linksCounter -= 1
|
||||
|
||||
if cachedLayer.linksCounter != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
let layer = cachedLayer.layer
|
||||
layerCache.removeValue(forKey: node)
|
||||
sceneLayer.setNeedsDisplay()
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
|
||||
func isAnimating(_ node: Node) -> Bool {
|
||||
|
||||
if let _ = layerCache[node] {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isChildrenAnimating(_ group: Group) -> Bool {
|
||||
|
||||
for child in group.contents {
|
||||
if isAnimating(child) {
|
||||
return true
|
||||
}
|
||||
|
||||
if let childGroup = child as? Group {
|
||||
return isChildrenAnimating(childGroup)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func containsAnimation(_ node: Node) -> Bool {
|
||||
if isAnimating(node) {
|
||||
return true
|
||||
}
|
||||
|
||||
if let group = node as? Group {
|
||||
return isChildrenAnimating(group)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func animations() -> [Animation] {
|
||||
|
||||
return layerCache.map ({ $0.1.animation })
|
||||
}
|
||||
|
||||
func replace(original: Node, replacement: Node) {
|
||||
guard let layer = layerCache[original] else {
|
||||
return
|
||||
}
|
||||
|
||||
layerCache[replacement] = layer
|
||||
layerCache.removeValue(forKey: original)
|
||||
cachedLayer.linksCounter += 1
|
||||
|
||||
return cachedLayer.layer
|
||||
}
|
||||
|
||||
private func calculateAnimationScale(animation: Animation) -> CGFloat {
|
||||
let defaultScale = UIScreen.main.scale
|
||||
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return defaultScale
|
||||
}
|
||||
|
||||
let animFunc = transformAnimation.getVFunc()
|
||||
let origBounds = Rect(x: 0.0, y: 0.0, w: 1.0, h: 1.0)
|
||||
|
||||
let startTransform = animFunc(0.0)
|
||||
let startBounds = origBounds.applyTransform(startTransform)
|
||||
var startArea = startBounds.w * startBounds.h
|
||||
|
||||
// zero scale protection
|
||||
if startArea == 0.0 {
|
||||
startArea = 0.1
|
||||
}
|
||||
|
||||
var maxArea = startArea
|
||||
var t = 0.0
|
||||
let step = 0.1
|
||||
while t <= 1.0 {
|
||||
let currentTransform = animFunc(t)
|
||||
let currentBounds = origBounds.applyTransform(currentTransform)
|
||||
let currentArea = currentBounds.w * currentBounds.h
|
||||
if maxArea < currentArea {
|
||||
maxArea = currentArea
|
||||
}
|
||||
|
||||
t = t + step
|
||||
}
|
||||
|
||||
return defaultScale * CGFloat(sqrt(maxArea))
|
||||
}
|
||||
|
||||
func freeLayer(_ node: Node) {
|
||||
guard let cachedLayer = layerCache[node] else {
|
||||
return
|
||||
}
|
||||
|
||||
cachedLayer.linksCounter -= 1
|
||||
|
||||
if cachedLayer.linksCounter != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
let layer = cachedLayer.layer
|
||||
layerCache.removeValue(forKey: node)
|
||||
sceneLayer.setNeedsDisplay()
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
|
||||
func isAnimating(_ node: Node) -> Bool {
|
||||
|
||||
if let _ = layerCache[node] {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isChildrenAnimating(_ group: Group) -> Bool {
|
||||
|
||||
for child in group.contents {
|
||||
if isAnimating(child) {
|
||||
return true
|
||||
}
|
||||
|
||||
if let childGroup = child as? Group {
|
||||
return isChildrenAnimating(childGroup)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func containsAnimation(_ node: Node) -> Bool {
|
||||
if isAnimating(node) {
|
||||
return true
|
||||
}
|
||||
|
||||
if let group = node as? Group {
|
||||
return isChildrenAnimating(group)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func animations() -> [Animation] {
|
||||
|
||||
return layerCache.map ({ $0.1.animation })
|
||||
}
|
||||
|
||||
func replace(original: Node, replacement: Node) {
|
||||
guard let layer = layerCache[original] else {
|
||||
return
|
||||
}
|
||||
|
||||
layerCache[replacement] = layer
|
||||
layerCache.removeValue(forKey: original)
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,20 @@ import Foundation
|
||||
|
||||
let animationRestorer = AnimationRestorer()
|
||||
open class AnimationRestorer {
|
||||
typealias RestoreClosure = () -> ()
|
||||
var restoreClosures = [RestoreClosure]()
|
||||
|
||||
func addRestoreClosure(_ closure: @escaping RestoreClosure) {
|
||||
restoreClosures.append(closure)
|
||||
}
|
||||
|
||||
open class func restore() {
|
||||
DispatchQueue.main.async {
|
||||
animationRestorer.restoreClosures.forEach { restoreClosure in
|
||||
restoreClosure()
|
||||
}
|
||||
|
||||
animationRestorer.restoreClosures.removeAll()
|
||||
}
|
||||
}
|
||||
typealias RestoreClosure = () -> ()
|
||||
var restoreClosures = [RestoreClosure]()
|
||||
|
||||
func addRestoreClosure(_ closure: @escaping RestoreClosure) {
|
||||
restoreClosures.append(closure)
|
||||
}
|
||||
|
||||
open class func restore() {
|
||||
DispatchQueue.main.async {
|
||||
animationRestorer.restoreClosures.forEach { restoreClosure in
|
||||
restoreClosure()
|
||||
}
|
||||
|
||||
animationRestorer.restoreClosures.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
|
||||
|
||||
// TODO: Implement better hash
|
||||
|
||||
extension Node: Hashable {
|
||||
public var hashValue: Int {
|
||||
|
||||
return Unmanaged.passUnretained(self).toOpaque().hashValue
|
||||
}
|
||||
public var hashValue: Int {
|
||||
return Unmanaged.passUnretained(self).toOpaque().hashValue
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: Node, rhs: Node) -> Bool {
|
||||
return lhs === rhs
|
||||
return lhs === rhs
|
||||
}
|
||||
|
@ -1,21 +1,20 @@
|
||||
|
||||
|
||||
extension Transform: Hashable {
|
||||
public var hashValue: Int {
|
||||
return m11.hashValue ^
|
||||
m12.hashValue ^
|
||||
m21.hashValue ^
|
||||
m22.hashValue ^
|
||||
dx.hashValue ^
|
||||
dy.hashValue
|
||||
}
|
||||
public var hashValue: Int {
|
||||
return m11.hashValue ^
|
||||
m12.hashValue ^
|
||||
m21.hashValue ^
|
||||
m22.hashValue ^
|
||||
dx.hashValue ^
|
||||
dy.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: Transform, rhs: Transform) -> Bool {
|
||||
return lhs.m11 == rhs.m11 &&
|
||||
lhs.m12 == rhs.m12 &&
|
||||
lhs.m21 == rhs.m21 &&
|
||||
lhs.m22 == rhs.m22 &&
|
||||
lhs.dx == rhs.dx &&
|
||||
lhs.dy == rhs.dy
|
||||
return lhs.m11 == rhs.m11 &&
|
||||
lhs.m12 == rhs.m12 &&
|
||||
lhs.m21 == rhs.m21 &&
|
||||
lhs.m22 == rhs.m22 &&
|
||||
lhs.dx == rhs.dx &&
|
||||
lhs.dy == rhs.dy
|
||||
}
|
||||
|
@ -5,122 +5,123 @@
|
||||
// Created by Victor Sukochev on 24/01/2017.
|
||||
//
|
||||
//
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let morphingAnimation = animation as? MorphingAnimation else {
|
||||
return
|
||||
guard let morphingAnimation = animation as? MorphingAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let shape = animation.node as? Shape else {
|
||||
return
|
||||
}
|
||||
|
||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
||||
nodesMap.replace(node: shape, to: mutatingShape)
|
||||
animationCache?.replace(original: shape, replacement: mutatingShape)
|
||||
animation.node = mutatingShape
|
||||
|
||||
let fromLocus = morphingAnimation.getVFunc()(0.0)
|
||||
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
||||
|
||||
guard let layer = animationCache?.layerForNode(mutatingShape, animation: animation, shouldRenderContent: false) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
let generatedAnim = pathAnimation(
|
||||
from:fromLocus,
|
||||
to:toLocus,
|
||||
duration: duration,
|
||||
renderTransform: layer.renderTransform!)
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
generatedAnim.autoreverses = animation.autoreverses
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
if animation.manualStop {
|
||||
animation.progress = 0.0
|
||||
mutatingShape.form = morphingAnimation.getVFunc()(0.0)
|
||||
} else if finished {
|
||||
animation.progress = 1.0
|
||||
mutatingShape.form = morphingAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
guard let shape = animation.node as? Shape else {
|
||||
return
|
||||
animationCache?.freeLayer(mutatingShape)
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
||||
nodesMap.replace(node: shape, to: mutatingShape)
|
||||
animationCache?.replace(original: shape, replacement: mutatingShape)
|
||||
animation.node = mutatingShape
|
||||
|
||||
let fromLocus = morphingAnimation.getVFunc()(0.0)
|
||||
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
||||
|
||||
guard let layer = animationCache?.layerForNode(mutatingShape, animation: animation, shouldRenderContent: false) else {
|
||||
return
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
let generatedAnim = pathAnimation(
|
||||
from:fromLocus,
|
||||
to:toLocus,
|
||||
duration: duration,
|
||||
renderTransform: layer.renderTransform!)
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
generatedAnim.autoreverses = animation.autoreverses
|
||||
let t = Double(progress)
|
||||
mutatingShape.form = morphingAnimation.getVFunc()(t)
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
if animation.manualStop {
|
||||
animation.progress = 0.0
|
||||
mutatingShape.form = morphingAnimation.getVFunc()(0.0)
|
||||
} else if finished {
|
||||
animation.progress = 1.0
|
||||
mutatingShape.form = morphingAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
animationCache?.freeLayer(mutatingShape)
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
mutatingShape.form = morphingAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
layer.path = RenderUtils.toCGPath(fromLocus)
|
||||
|
||||
// Stroke
|
||||
if let stroke = mutatingShape.stroke {
|
||||
if let color = stroke.fill as? Color {
|
||||
layer.strokeColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.strokeColor = UIColor.black.cgColor
|
||||
}
|
||||
|
||||
layer.lineWidth = CGFloat(stroke.width)
|
||||
layer.lineCap = RenderUtils.mapLineCapToString(stroke.cap)
|
||||
layer.lineJoin = RenderUtils.mapLineJoinToString(stroke.join)
|
||||
layer.lineDashPattern = stroke.dashes.map{ NSNumber(value: $0)}
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
layer.path = RenderUtils.toCGPath(fromLocus)
|
||||
|
||||
// Stroke
|
||||
if let stroke = mutatingShape.stroke {
|
||||
if let color = stroke.fill as? Color {
|
||||
layer.strokeColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.strokeColor = UIColor.black.cgColor
|
||||
layer.lineWidth = 1.0
|
||||
layer.strokeColor = MColor.black.cgColor
|
||||
}
|
||||
|
||||
// Fill
|
||||
if let color = mutatingShape.fill as? Color {
|
||||
layer.fillColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.fillColor = UIColor.clear.cgColor
|
||||
}
|
||||
|
||||
layer.add(generatedAnim, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
layer.lineWidth = CGFloat(stroke.width)
|
||||
layer.lineCap = RenderUtils.mapLineCapToString(stroke.cap)
|
||||
layer.lineJoin = RenderUtils.mapLineJoinToString(stroke.join)
|
||||
layer.lineDashPattern = stroke.dashes.map{ NSNumber(value: $0)}
|
||||
} else {
|
||||
layer.strokeColor = MColor.black.cgColor
|
||||
layer.lineWidth = 1.0
|
||||
}
|
||||
|
||||
// Fill
|
||||
if let color = mutatingShape.fill as? Color {
|
||||
layer.fillColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.fillColor = MColor.clear.cgColor
|
||||
}
|
||||
|
||||
layer.add(generatedAnim, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func pathAnimation(from:Locus, to: Locus, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
|
||||
|
||||
var transform = renderTransform
|
||||
let fromPath = RenderUtils.toCGPath(from).copy(using: &transform)
|
||||
let toPath = RenderUtils.toCGPath(to).copy(using: &transform)
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "path")
|
||||
animation.fromValue = fromPath
|
||||
animation.toValue = toPath
|
||||
animation.duration = duration
|
||||
animation.fillMode = kCAFillModeForwards
|
||||
animation.isRemovedOnCompletion = false
|
||||
|
||||
return animation
|
||||
|
||||
var transform = renderTransform
|
||||
let fromPath = RenderUtils.toCGPath(from).copy(using: &transform)
|
||||
let toPath = RenderUtils.toCGPath(to).copy(using: &transform)
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "path")
|
||||
animation.fromValue = fromPath
|
||||
animation.toValue = toPath
|
||||
animation.duration = duration
|
||||
animation.fillMode = kCAFillModeForwards
|
||||
animation.isRemovedOnCompletion = false
|
||||
|
||||
return animation
|
||||
}
|
||||
|
@ -1,101 +1,103 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let opacityAnimation = animation as? OpacityAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = animation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
let generatedAnimation = opacityAnimationByFunc(opacityAnimation.getVFunc(),
|
||||
duration: animation.getDuration(),
|
||||
offset: animation.pausedProgress,
|
||||
fps: opacityAnimation.logicalFps)
|
||||
generatedAnimation.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||
|
||||
generatedAnimation.completion = { finished in
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
if animation.paused {
|
||||
animation.pausedProgress = animation.pausedProgress + animation.progress
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(animation.pausedProgress)
|
||||
} else if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 0.0
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(0.0)
|
||||
} else if finished {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 1.0
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop &&
|
||||
!animation.paused {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnimation.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
if let layer = animationCache?.layerForNode(node, animation: animation) {
|
||||
layer.add(generatedAnimation, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
guard let opacityAnimation = animation as? OpacityAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = animation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
let generatedAnimation = opacityAnimationByFunc(opacityAnimation.getVFunc(),
|
||||
duration: animation.getDuration(),
|
||||
offset: animation.pausedProgress,
|
||||
fps: opacityAnimation.logicalFps)
|
||||
generatedAnimation.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||
|
||||
generatedAnimation.completion = { finished in
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
if animation.paused {
|
||||
animation.pausedProgress = animation.pausedProgress + animation.progress
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(animation.pausedProgress)
|
||||
} else if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 0.0
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(0.0)
|
||||
} else if finished {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 1.0
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop &&
|
||||
!animation.paused {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnimation.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
if let layer = animationCache?.layerForNode(node, animation: animation) {
|
||||
layer.add(generatedAnimation, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func opacityAnimationByFunc(_ valueFunc: (Double) -> Double, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
|
||||
|
||||
var opacityValues = [Double]()
|
||||
var timeValues = [Double]()
|
||||
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
|
||||
var dt = 0.0
|
||||
var tValue = Array(stride(from: 0.0, to: 1.0, by: step))
|
||||
tValue.append(1.0)
|
||||
for t in tValue {
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
let value = valueFunc(offset + dt)
|
||||
opacityValues.append(value)
|
||||
timeValues.append(dt)
|
||||
}
|
||||
|
||||
let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
opacityAnimation.fillMode = kCAFillModeForwards
|
||||
opacityAnimation.isRemovedOnCompletion = false
|
||||
|
||||
opacityAnimation.duration = duration
|
||||
opacityAnimation.values = opacityValues
|
||||
opacityAnimation.keyTimes = timeValues as [NSNumber]?
|
||||
|
||||
return opacityAnimation
|
||||
|
||||
var opacityValues = [Double]()
|
||||
var timeValues = [Double]()
|
||||
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
|
||||
var dt = 0.0
|
||||
var tValue = Array(stride(from: 0.0, to: 1.0, by: step))
|
||||
tValue.append(1.0)
|
||||
for t in tValue {
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
let value = valueFunc(offset + dt)
|
||||
opacityValues.append(value)
|
||||
timeValues.append(dt)
|
||||
}
|
||||
|
||||
let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
opacityAnimation.fillMode = kCAFillModeForwards
|
||||
opacityAnimation.isRemovedOnCompletion = false
|
||||
|
||||
opacityAnimation.duration = duration
|
||||
opacityAnimation.values = opacityValues
|
||||
opacityAnimation.keyTimes = timeValues as [NSNumber]?
|
||||
|
||||
return opacityAnimation
|
||||
}
|
||||
|
@ -5,194 +5,195 @@
|
||||
// Created by Victor Sukochev on 03/02/2017.
|
||||
//
|
||||
//
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let shapeAnimation = animation as? ShapeAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let shape = animation.node as? Shape else {
|
||||
return
|
||||
}
|
||||
|
||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
||||
nodesMap.replace(node: shape, to: mutatingShape)
|
||||
animationCache?.replace(original: shape, replacement: mutatingShape)
|
||||
animation.node = mutatingShape
|
||||
|
||||
let fromShape = shapeAnimation.getVFunc()(0.0)
|
||||
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
||||
guard let shapeAnimation = animation as? ShapeAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let layer = animationCache?.layerForNode(mutatingShape, animation: animation, shouldRenderContent: false) else {
|
||||
return
|
||||
guard let shape = animation.node as? Shape else {
|
||||
return
|
||||
}
|
||||
|
||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
||||
nodesMap.replace(node: shape, to: mutatingShape)
|
||||
animationCache?.replace(original: shape, replacement: mutatingShape)
|
||||
animation.node = mutatingShape
|
||||
|
||||
let fromShape = shapeAnimation.getVFunc()(0.0)
|
||||
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
||||
|
||||
guard let layer = animationCache?.layerForNode(mutatingShape, animation: animation, shouldRenderContent: false) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
let generatedAnim = generateShapAnimation(
|
||||
from:fromShape,
|
||||
to:toShape,
|
||||
duration: duration,
|
||||
renderTransform: layer.renderTransform!)
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
generatedAnim.autoreverses = animation.autoreverses
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
animation.progress = animation.manualStop ? 0.0 : 1.0
|
||||
|
||||
if !animation.autoreverses && finished {
|
||||
mutatingShape.form = toShape.form
|
||||
mutatingShape.stroke = toShape.stroke
|
||||
mutatingShape.fill = toShape.fill
|
||||
}
|
||||
|
||||
|
||||
// Creating proper animation
|
||||
let generatedAnim = generateShapAnimation(
|
||||
from:fromShape,
|
||||
to:toShape,
|
||||
duration: duration,
|
||||
renderTransform: layer.renderTransform!)
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
generatedAnim.autoreverses = animation.autoreverses
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
animation.progress = animation.manualStop ? 0.0 : 1.0
|
||||
|
||||
if !animation.autoreverses && finished {
|
||||
mutatingShape.form = toShape.form
|
||||
mutatingShape.stroke = toShape.stroke
|
||||
mutatingShape.fill = toShape.fill
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animation.progress = 0.0
|
||||
mutatingShape.form = fromShape.form
|
||||
mutatingShape.stroke = fromShape.stroke
|
||||
mutatingShape.fill = fromShape.fill
|
||||
}
|
||||
|
||||
animationCache?.freeLayer(mutatingShape)
|
||||
|
||||
if !animation.cycled && !animation.manualStop {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
if !finished {
|
||||
animation.progress = 0.0
|
||||
mutatingShape.form = fromShape.form
|
||||
mutatingShape.stroke = fromShape.stroke
|
||||
mutatingShape.fill = fromShape.fill
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
|
||||
if !animation.autoreverses {
|
||||
let currentShape = shapeAnimation.getVFunc()(t)
|
||||
mutatingShape.form = currentShape.form
|
||||
mutatingShape.stroke = currentShape.stroke
|
||||
mutatingShape.fill = currentShape.fill
|
||||
}
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
animationCache?.freeLayer(mutatingShape)
|
||||
|
||||
if !animation.cycled && !animation.manualStop {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
layer.path = RenderUtils.toCGPath(fromShape.form)
|
||||
|
||||
// Stroke
|
||||
if let stroke = shape.stroke {
|
||||
if let color = stroke.fill as? Color {
|
||||
layer.strokeColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.strokeColor = UIColor.black.cgColor
|
||||
}
|
||||
|
||||
layer.lineWidth = CGFloat(stroke.width)
|
||||
layer.lineCap = RenderUtils.mapLineCapToString(stroke.cap)
|
||||
layer.lineJoin = RenderUtils.mapLineJoinToString(stroke.join)
|
||||
layer.lineDashPattern = stroke.dashes.map{ NSNumber(value: $0)}
|
||||
} else if shape.fill == nil {
|
||||
layer.strokeColor = UIColor.black.cgColor
|
||||
layer.lineWidth = 1.0
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
// Fill
|
||||
if let color = shape.fill as? Color {
|
||||
layer.fillColor = RenderUtils.mapColor(color)
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
|
||||
if !animation.autoreverses {
|
||||
let currentShape = shapeAnimation.getVFunc()(t)
|
||||
mutatingShape.form = currentShape.form
|
||||
mutatingShape.stroke = currentShape.stroke
|
||||
mutatingShape.fill = currentShape.fill
|
||||
}
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
layer.path = RenderUtils.toCGPath(fromShape.form)
|
||||
|
||||
// Stroke
|
||||
if let stroke = shape.stroke {
|
||||
if let color = stroke.fill as? Color {
|
||||
layer.strokeColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.fillColor = UIColor.clear.cgColor
|
||||
layer.strokeColor = MColor.black.cgColor
|
||||
}
|
||||
|
||||
layer.add(generatedAnim, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
layer.lineWidth = CGFloat(stroke.width)
|
||||
layer.lineCap = RenderUtils.mapLineCapToString(stroke.cap)
|
||||
layer.lineJoin = RenderUtils.mapLineJoinToString(stroke.join)
|
||||
layer.lineDashPattern = stroke.dashes.map{ NSNumber(value: $0)}
|
||||
} else if shape.fill == nil {
|
||||
layer.strokeColor = MColor.black.cgColor
|
||||
layer.lineWidth = 1.0
|
||||
}
|
||||
|
||||
// Fill
|
||||
if let color = shape.fill as? Color {
|
||||
layer.fillColor = RenderUtils.mapColor(color)
|
||||
} else {
|
||||
layer.fillColor = MColor.clear.cgColor
|
||||
}
|
||||
|
||||
layer.add(generatedAnim, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func generateShapAnimation(from:Shape, to: Shape, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
|
||||
|
||||
let group = CAAnimationGroup()
|
||||
|
||||
// Shape
|
||||
// Path
|
||||
var transform = renderTransform
|
||||
let fromPath = RenderUtils.toCGPath(from.form).copy(using: &transform)
|
||||
let toPath = RenderUtils.toCGPath(to.form).copy(using: &transform)
|
||||
|
||||
let pathAnimation = CABasicAnimation(keyPath: "path")
|
||||
pathAnimation.fromValue = fromPath
|
||||
pathAnimation.toValue = toPath
|
||||
pathAnimation.duration = duration
|
||||
|
||||
group.animations = [pathAnimation]
|
||||
|
||||
// Fill
|
||||
let fromFillColor = from.fill as? Color ?? Color.clear
|
||||
let toFillColor = to.fill as? Color ?? Color.clear
|
||||
|
||||
if fromFillColor != toFillColor {
|
||||
let fillAnimation = CABasicAnimation(keyPath: "fillColor")
|
||||
fillAnimation.fromValue = RenderUtils.mapColor(fromFillColor)
|
||||
fillAnimation.toValue = RenderUtils.mapColor(toFillColor)
|
||||
fillAnimation.duration = duration
|
||||
|
||||
let group = CAAnimationGroup()
|
||||
group.animations?.append(fillAnimation)
|
||||
}
|
||||
|
||||
|
||||
// Stroke
|
||||
let fromStroke = from.stroke ?? Stroke(fill: Color.black, width: 1.0)
|
||||
let toStroke = to.stroke ?? Stroke(fill: Color.black, width: 1.0)
|
||||
|
||||
// Line width
|
||||
if fromStroke.width != toStroke.width {
|
||||
let strokeWidthAnimation = CABasicAnimation(keyPath: "lineWidth")
|
||||
strokeWidthAnimation.fromValue = fromStroke.width
|
||||
strokeWidthAnimation.toValue = toStroke.width
|
||||
strokeWidthAnimation.duration = duration
|
||||
|
||||
// Shape
|
||||
// Path
|
||||
var transform = renderTransform
|
||||
let fromPath = RenderUtils.toCGPath(from.form).copy(using: &transform)
|
||||
let toPath = RenderUtils.toCGPath(to.form).copy(using: &transform)
|
||||
group.animations?.append(strokeWidthAnimation)
|
||||
}
|
||||
|
||||
// Line color
|
||||
let fromStrokeColor = fromStroke.fill as? Color ?? Color.black
|
||||
let toStrokeColor = toStroke.fill as? Color ?? Color.black
|
||||
|
||||
if fromStrokeColor != toStrokeColor {
|
||||
let strokeColorAnimation = CABasicAnimation(keyPath: "strokeColor")
|
||||
strokeColorAnimation.fromValue = RenderUtils.mapColor(fromStrokeColor)
|
||||
strokeColorAnimation.toValue = RenderUtils.mapColor(toStrokeColor)
|
||||
strokeColorAnimation.duration = duration
|
||||
|
||||
let pathAnimation = CABasicAnimation(keyPath: "path")
|
||||
pathAnimation.fromValue = fromPath
|
||||
pathAnimation.toValue = toPath
|
||||
pathAnimation.duration = duration
|
||||
group.animations?.append(strokeColorAnimation)
|
||||
}
|
||||
|
||||
// Dash pattern
|
||||
if fromStroke.dashes != toStroke.dashes {
|
||||
let dashPatternAnimation = CABasicAnimation(keyPath: "lineDashPattern")
|
||||
dashPatternAnimation.fromValue = fromStroke.dashes
|
||||
dashPatternAnimation.toValue = toStroke.dashes
|
||||
dashPatternAnimation.duration = duration
|
||||
|
||||
group.animations = [pathAnimation]
|
||||
|
||||
// Fill
|
||||
let fromFillColor = from.fill as? Color ?? Color.clear
|
||||
let toFillColor = to.fill as? Color ?? Color.clear
|
||||
|
||||
if fromFillColor != toFillColor {
|
||||
let fillAnimation = CABasicAnimation(keyPath: "fillColor")
|
||||
fillAnimation.fromValue = RenderUtils.mapColor(fromFillColor)
|
||||
fillAnimation.toValue = RenderUtils.mapColor(toFillColor)
|
||||
fillAnimation.duration = duration
|
||||
|
||||
group.animations?.append(fillAnimation)
|
||||
}
|
||||
|
||||
|
||||
// Stroke
|
||||
let fromStroke = from.stroke ?? Stroke(fill: Color.black, width: 1.0)
|
||||
let toStroke = to.stroke ?? Stroke(fill: Color.black, width: 1.0)
|
||||
|
||||
// Line width
|
||||
if fromStroke.width != toStroke.width {
|
||||
let strokeWidthAnimation = CABasicAnimation(keyPath: "lineWidth")
|
||||
strokeWidthAnimation.fromValue = fromStroke.width
|
||||
strokeWidthAnimation.toValue = toStroke.width
|
||||
strokeWidthAnimation.duration = duration
|
||||
|
||||
group.animations?.append(strokeWidthAnimation)
|
||||
}
|
||||
|
||||
// Line color
|
||||
let fromStrokeColor = fromStroke.fill as? Color ?? Color.black
|
||||
let toStrokeColor = toStroke.fill as? Color ?? Color.black
|
||||
|
||||
if fromStrokeColor != toStrokeColor {
|
||||
let strokeColorAnimation = CABasicAnimation(keyPath: "strokeColor")
|
||||
strokeColorAnimation.fromValue = RenderUtils.mapColor(fromStrokeColor)
|
||||
strokeColorAnimation.toValue = RenderUtils.mapColor(toStrokeColor)
|
||||
strokeColorAnimation.duration = duration
|
||||
|
||||
group.animations?.append(strokeColorAnimation)
|
||||
}
|
||||
|
||||
// Dash pattern
|
||||
if fromStroke.dashes != toStroke.dashes {
|
||||
let dashPatternAnimation = CABasicAnimation(keyPath: "lineDashPattern")
|
||||
dashPatternAnimation.fromValue = fromStroke.dashes
|
||||
dashPatternAnimation.toValue = toStroke.dashes
|
||||
dashPatternAnimation.duration = duration
|
||||
|
||||
group.animations?.append(dashPatternAnimation)
|
||||
}
|
||||
|
||||
// Group
|
||||
group.duration = duration
|
||||
group.fillMode = kCAFillModeForwards
|
||||
group.isRemovedOnCompletion = false
|
||||
|
||||
return group
|
||||
group.animations?.append(dashPatternAnimation)
|
||||
}
|
||||
|
||||
// Group
|
||||
group.duration = duration
|
||||
group.fillMode = kCAFillModeForwards
|
||||
group.isRemovedOnCompletion = false
|
||||
|
||||
return group
|
||||
}
|
||||
|
@ -1,39 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func caTimingFunction(_ easing: Easing) -> CAMediaTimingFunction {
|
||||
switch easing {
|
||||
case .ease:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
|
||||
case .linear:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
||||
case .easeIn:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
|
||||
case .easeOut:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
|
||||
case .easeInOut:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
switch easing {
|
||||
case .ease:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
|
||||
case .linear:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
||||
case .easeIn:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
|
||||
case .easeOut:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
|
||||
case .easeInOut:
|
||||
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
}
|
||||
|
||||
func progressForTimingFunction(_ easing: Easing, progress: Double) -> Double {
|
||||
let t = progress
|
||||
|
||||
switch easing {
|
||||
case .ease:
|
||||
return t
|
||||
case .linear:
|
||||
return t
|
||||
case .easeIn:
|
||||
return t * t
|
||||
case .easeOut:
|
||||
return -(t * (t - 2))
|
||||
case .easeInOut:
|
||||
if t < 0.5 {
|
||||
return 2.0 * t * t
|
||||
} else {
|
||||
return -2.0 * t * t + 4.0 * t - 1.0
|
||||
}
|
||||
let t = progress
|
||||
|
||||
switch easing {
|
||||
case .ease:
|
||||
return t
|
||||
case .linear:
|
||||
return t
|
||||
case .easeIn:
|
||||
return t * t
|
||||
case .easeOut:
|
||||
return -(t * (t - 2))
|
||||
case .easeInOut:
|
||||
if t < 0.5 {
|
||||
return 2.0 * t * t
|
||||
} else {
|
||||
return -2.0 * t * t + 4.0 * t - 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,122 +1,124 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = animation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
var generatedAnimation: CAAnimation?
|
||||
|
||||
generatedAnimation = transformAnimationByFunc(node,
|
||||
valueFunc: transformAnimation.getVFunc(),
|
||||
duration: animation.getDuration(),
|
||||
offset: animation.pausedProgress,
|
||||
fps: transformAnimation.logicalFps)
|
||||
|
||||
guard let generatedAnim = generatedAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
if animation.paused {
|
||||
animation.pausedProgress = animation.pausedProgress + animation.progress
|
||||
node.placeVar.value = transformAnimation.getVFunc()(animation.pausedProgress)
|
||||
} else if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 0.0
|
||||
node.placeVar.value = transformAnimation.getVFunc()(0.0)
|
||||
} else if finished {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 1.0
|
||||
node.placeVar.value = transformAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop &&
|
||||
!animation.paused {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
node.placeVar.value = transformAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
if let layer = animationCache?.layerForNode(node, animation: animation) {
|
||||
layer.add(generatedAnim, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = animation.node else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
var generatedAnimation: CAAnimation?
|
||||
|
||||
generatedAnimation = transformAnimationByFunc(node,
|
||||
valueFunc: transformAnimation.getVFunc(),
|
||||
duration: animation.getDuration(),
|
||||
offset: animation.pausedProgress,
|
||||
fps: transformAnimation.logicalFps)
|
||||
|
||||
guard let generatedAnim = generatedAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
if animation.paused {
|
||||
animation.pausedProgress = animation.pausedProgress + animation.progress
|
||||
node.placeVar.value = transformAnimation.getVFunc()(animation.pausedProgress)
|
||||
} else if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 0.0
|
||||
node.placeVar.value = transformAnimation.getVFunc()(0.0)
|
||||
} else if finished {
|
||||
animation.pausedProgress = 0.0
|
||||
animation.progress = 1.0
|
||||
node.placeVar.value = transformAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop &&
|
||||
!animation.paused {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
node.placeVar.value = transformAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
if let layer = animationCache?.layerForNode(node, animation: animation) {
|
||||
layer.add(generatedAnim, forKey: animation.ID)
|
||||
animation.removeFunc = {
|
||||
layer.removeAnimation(forKey: animation.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func transfomToCG(_ transform: Transform) -> CGAffineTransform {
|
||||
return CGAffineTransform(
|
||||
a: CGFloat(transform.m11),
|
||||
b: CGFloat(transform.m12),
|
||||
c: CGFloat(transform.m21),
|
||||
d: CGFloat(transform.m22),
|
||||
tx: CGFloat(transform.dx),
|
||||
ty: CGFloat(transform.dy))
|
||||
return CGAffineTransform(
|
||||
a: CGFloat(transform.m11),
|
||||
b: CGFloat(transform.m12),
|
||||
c: CGFloat(transform.m21),
|
||||
d: CGFloat(transform.m22),
|
||||
tx: CGFloat(transform.dx),
|
||||
ty: CGFloat(transform.dy))
|
||||
}
|
||||
|
||||
func transformAnimationByFunc(_ node: Node, valueFunc: (Double) -> Transform, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
|
||||
|
||||
var transformValues = [CATransform3D]()
|
||||
var timeValues = [Double]()
|
||||
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
var dt = 0.0
|
||||
var tValue = Array(stride(from: 0.0, to: 1.0, by: step))
|
||||
tValue.append(1.0)
|
||||
for t in tValue {
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
timeValues.append(dt)
|
||||
let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + dt))
|
||||
let cgValue = CATransform3DMakeAffineTransform(RenderUtils.mapTransform(value))
|
||||
transformValues.append(cgValue)
|
||||
}
|
||||
|
||||
let transformAnimation = CAKeyframeAnimation(keyPath: "transform")
|
||||
transformAnimation.duration = duration
|
||||
transformAnimation.values = transformValues
|
||||
transformAnimation.keyTimes = timeValues as [NSNumber]?
|
||||
transformAnimation.fillMode = kCAFillModeForwards
|
||||
transformAnimation.isRemovedOnCompletion = false
|
||||
|
||||
return transformAnimation
|
||||
|
||||
var transformValues = [CATransform3D]()
|
||||
var timeValues = [Double]()
|
||||
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
var dt = 0.0
|
||||
var tValue = Array(stride(from: 0.0, to: 1.0, by: step))
|
||||
tValue.append(1.0)
|
||||
for t in tValue {
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
timeValues.append(dt)
|
||||
let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + dt))
|
||||
let cgValue = CATransform3DMakeAffineTransform(RenderUtils.mapTransform(value))
|
||||
transformValues.append(cgValue)
|
||||
}
|
||||
|
||||
let transformAnimation = CAKeyframeAnimation(keyPath: "transform")
|
||||
transformAnimation.duration = duration
|
||||
transformAnimation.values = transformValues
|
||||
transformAnimation.keyTimes = timeValues as [NSNumber]?
|
||||
transformAnimation.fillMode = kCAFillModeForwards
|
||||
transformAnimation.isRemovedOnCompletion = false
|
||||
|
||||
return transformAnimation
|
||||
}
|
||||
|
||||
func fixedAngle(_ angle: CGFloat) -> CGFloat {
|
||||
return angle > -0.0000000000000000000000001 ? angle : CGFloat(2.0 * Double.pi) + angle
|
||||
return angle > -0.0000000000000000000000001 ? angle : CGFloat(2.0 * Double.pi) + angle
|
||||
}
|
||||
|
@ -9,14 +9,14 @@
|
||||
import Foundation
|
||||
|
||||
open class Disposable {
|
||||
|
||||
let handler: (()->())
|
||||
|
||||
init (_ disposeHandler: @escaping (()->()) ) {
|
||||
handler = disposeHandler
|
||||
}
|
||||
|
||||
open func dispose() {
|
||||
handler()
|
||||
}
|
||||
|
||||
let handler: (()->())
|
||||
|
||||
init (_ disposeHandler: @escaping (()->()) ) {
|
||||
handler = disposeHandler
|
||||
}
|
||||
|
||||
open func dispose() {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
@ -6,26 +6,25 @@
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
open class GroupDisposable {
|
||||
|
||||
fileprivate var items: [Disposable] = []
|
||||
|
||||
open func dispose() {
|
||||
for disposable in items {
|
||||
disposable.dispose()
|
||||
}
|
||||
items = []
|
||||
}
|
||||
|
||||
open func add(_ item: Disposable) {
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
|
||||
fileprivate var items: [Disposable] = []
|
||||
|
||||
open func dispose() {
|
||||
for disposable in items {
|
||||
disposable.dispose()
|
||||
}
|
||||
items = []
|
||||
}
|
||||
|
||||
open func add(_ item: Disposable) {
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Disposable {
|
||||
public func addTo(_ group: GroupDisposable) {
|
||||
group.add(self)
|
||||
}
|
||||
public func addTo(_ group: GroupDisposable) {
|
||||
group.add(self)
|
||||
}
|
||||
}
|
||||
|
@ -9,39 +9,39 @@
|
||||
import Foundation
|
||||
|
||||
class ChangeHandler<T>: Equatable {
|
||||
let handle: ((T)->())
|
||||
|
||||
init(_ f: @escaping ((T)->()) ) {
|
||||
handle = f
|
||||
}
|
||||
|
||||
static func ==(lhs: ChangeHandler<T>, rhs: ChangeHandler<T>) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
let handle: ((T)->())
|
||||
|
||||
init(_ f: @escaping ((T)->()) ) {
|
||||
handle = f
|
||||
}
|
||||
|
||||
static func ==(lhs: ChangeHandler<T>, rhs: ChangeHandler<T>) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
|
||||
open class Variable<T> {
|
||||
var handlers = [ChangeHandler<T>]()
|
||||
|
||||
open var value: T {
|
||||
didSet {
|
||||
handlers.forEach { handler in handler.handle(value) }
|
||||
}
|
||||
}
|
||||
|
||||
init(_ v: T) {
|
||||
value = v
|
||||
}
|
||||
|
||||
@discardableResult open func onChange(_ f: @escaping ((T)->())) -> Disposable {
|
||||
let handler = ChangeHandler<T>(f)
|
||||
handlers.append(handler)
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.handlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.handlers.remove(at: index)
|
||||
})
|
||||
var handlers = [ChangeHandler<T>]()
|
||||
|
||||
open var value: T {
|
||||
didSet {
|
||||
handlers.forEach { handler in handler.handle(value) }
|
||||
}
|
||||
}
|
||||
|
||||
init(_ v: T) {
|
||||
value = v
|
||||
}
|
||||
|
||||
@discardableResult open func onChange(_ f: @escaping ((T)->())) -> Disposable {
|
||||
let handler = ChangeHandler<T>(f)
|
||||
handlers.append(handler)
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.handlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.handlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,17 +9,16 @@
|
||||
import Foundation
|
||||
|
||||
open class Event {
|
||||
|
||||
public weak var node: Node?
|
||||
|
||||
var consumed = false
|
||||
|
||||
init(node: Node) {
|
||||
self.node = node
|
||||
}
|
||||
|
||||
public func consume() {
|
||||
consumed = true
|
||||
}
|
||||
|
||||
|
||||
public weak var node: Node?
|
||||
|
||||
var consumed = false
|
||||
|
||||
init(node: Node) {
|
||||
self.node = node
|
||||
}
|
||||
|
||||
public func consume() {
|
||||
consumed = true
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
open class PanEvent : Event {
|
||||
|
||||
open let dx: Double
|
||||
open let dy: Double
|
||||
open let count: Int
|
||||
|
||||
init(node: Node, dx: Double, dy: Double, count: Int) {
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
self.count = count
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
|
||||
open let dx: Double
|
||||
open let dy: Double
|
||||
open let count: Int
|
||||
|
||||
init(node: Node, dx: Double, dy: Double, count: Int) {
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
self.count = count
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
open class PinchEvent : Event {
|
||||
|
||||
open let scale: Double
|
||||
|
||||
init(node: Node, scale: Double) {
|
||||
self.scale = scale
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
|
||||
open let scale: Double
|
||||
|
||||
init(node: Node, scale: Double) {
|
||||
self.scale = scale
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
open class RotateEvent : Event {
|
||||
|
||||
open let angle: Double
|
||||
|
||||
init(node: Node, angle: Double) {
|
||||
self.angle = angle
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
|
||||
open let angle: Double
|
||||
|
||||
init(node: Node, angle: Double) {
|
||||
self.angle = angle
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
open class TapEvent : Event {
|
||||
|
||||
open let location: Point
|
||||
|
||||
init(node: Node, location: Point) {
|
||||
self.location = location
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
|
||||
open let location: Point
|
||||
|
||||
init(node: Node, location: Point) {
|
||||
self.location = location
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,17 +8,17 @@
|
||||
|
||||
|
||||
public struct TouchPoint {
|
||||
public let id: Int
|
||||
public let location: Point
|
||||
public let id: Int
|
||||
public let location: Point
|
||||
}
|
||||
|
||||
public class TouchEvent : Event {
|
||||
|
||||
public let points: [TouchPoint]
|
||||
|
||||
public init(node: Node, points: [TouchPoint]) {
|
||||
self.points = points
|
||||
|
||||
public let points: [TouchPoint]
|
||||
|
||||
public init(node: Node, points: [TouchPoint]) {
|
||||
self.points = points
|
||||
|
||||
super.init(node: node)
|
||||
}
|
||||
super.init(node: node)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
public enum Align {
|
||||
case min
|
||||
case mid
|
||||
case max
|
||||
case min
|
||||
case mid
|
||||
case max
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
public enum AspectRatio {
|
||||
case none
|
||||
case meet
|
||||
case slice
|
||||
case none
|
||||
case meet
|
||||
case slice
|
||||
}
|
||||
|
@ -1,72 +1,72 @@
|
||||
import Foundation
|
||||
|
||||
open class Color: Fill, Equatable {
|
||||
|
||||
open let val: Int
|
||||
|
||||
open static let white: Color = Color( val: 0xFFFFFF )
|
||||
open static let silver: Color = Color( val: 0xC0C0C0 )
|
||||
open static let gray: Color = Color( val: 0x808080 )
|
||||
open static let black: Color = Color( val: 0 )
|
||||
open static let red: Color = Color( val: 0xFF0000 )
|
||||
open static let maroon: Color = Color( val: 0x800000 )
|
||||
open static let yellow: Color = Color( val: 0xFFFF00 )
|
||||
open static let olive: Color = Color( val: 0x808000 )
|
||||
open static let lime: Color = Color( val: 0x00FF00 )
|
||||
open static let green: Color = Color( val: 0x008000 )
|
||||
open static let aqua: Color = Color( val: 0x00FFFF )
|
||||
open static let teal: Color = Color( val: 0x008080 )
|
||||
open static let blue: Color = Color( val: 0x0000FF )
|
||||
open static let navy: Color = Color( val: 0x000080 )
|
||||
open static let fuchsia: Color = Color( val: 0xFF00FF )
|
||||
open static let purple: Color = Color( val: 0x800080 )
|
||||
open static let clear: Color = Color.rgba(r: 0, g: 0, b: 0, a: 0)
|
||||
|
||||
public init(val: Int = 0) {
|
||||
self.val = val
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func r() -> Int {
|
||||
return ( ( val >> 16 ) & 0xff )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func g() -> Int {
|
||||
return ( ( val >> 8 ) & 0xff )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func b() -> Int {
|
||||
return ( val & 0xff )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func a() -> Int {
|
||||
return ( 255 - ( ( val >> 24 ) & 0xff ) )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public func with(a: Double) -> Color {
|
||||
return Color.rgba(r: r(), g: g(), b: b(), a: a)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open class func rgbt(r: Int, g: Int, b: Int, t: Int) -> Color {
|
||||
return Color( val: ( ( ( ( ( t & 0xff ) << 24 ) | ( ( r & 0xff ) << 16 ) ) | ( ( g & 0xff ) << 8 ) ) | ( b & 0xff ) ) )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open class func rgba(r: Int, g: Int, b: Int, a: Double) -> Color {
|
||||
return rgbt( r: r, g: g, b: b, t: Int( ( ( 1 - a ) * 255 ) ) )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open class func rgb(r: Int, g: Int, b: Int) -> Color {
|
||||
return rgbt( r: r, g: g, b: b, t: 0 )
|
||||
}
|
||||
|
||||
public static func ==(lhs: Color, rhs: Color) -> Bool {
|
||||
return lhs.val == rhs.val
|
||||
}
|
||||
|
||||
open let val: Int
|
||||
|
||||
open static let white: Color = Color( val: 0xFFFFFF )
|
||||
open static let silver: Color = Color( val: 0xC0C0C0 )
|
||||
open static let gray: Color = Color( val: 0x808080 )
|
||||
open static let black: Color = Color( val: 0 )
|
||||
open static let red: Color = Color( val: 0xFF0000 )
|
||||
open static let maroon: Color = Color( val: 0x800000 )
|
||||
open static let yellow: Color = Color( val: 0xFFFF00 )
|
||||
open static let olive: Color = Color( val: 0x808000 )
|
||||
open static let lime: Color = Color( val: 0x00FF00 )
|
||||
open static let green: Color = Color( val: 0x008000 )
|
||||
open static let aqua: Color = Color( val: 0x00FFFF )
|
||||
open static let teal: Color = Color( val: 0x008080 )
|
||||
open static let blue: Color = Color( val: 0x0000FF )
|
||||
open static let navy: Color = Color( val: 0x000080 )
|
||||
open static let fuchsia: Color = Color( val: 0xFF00FF )
|
||||
open static let purple: Color = Color( val: 0x800080 )
|
||||
open static let clear: Color = Color.rgba(r: 0, g: 0, b: 0, a: 0)
|
||||
|
||||
public init(val: Int = 0) {
|
||||
self.val = val
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func r() -> Int {
|
||||
return ( ( val >> 16 ) & 0xff )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func g() -> Int {
|
||||
return ( ( val >> 8 ) & 0xff )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func b() -> Int {
|
||||
return ( val & 0xff )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open func a() -> Int {
|
||||
return ( 255 - ( ( val >> 24 ) & 0xff ) )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public func with(a: Double) -> Color {
|
||||
return Color.rgba(r: r(), g: g(), b: b(), a: a)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open class func rgbt(r: Int, g: Int, b: Int, t: Int) -> Color {
|
||||
return Color( val: ( ( ( ( ( t & 0xff ) << 24 ) | ( ( r & 0xff ) << 16 ) ) | ( ( g & 0xff ) << 8 ) ) | ( b & 0xff ) ) )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open class func rgba(r: Int, g: Int, b: Int, a: Double) -> Color {
|
||||
return rgbt( r: r, g: g, b: b, t: Int( ( ( 1 - a ) * 255 ) ) )
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
open class func rgb(r: Int, g: Int, b: Int) -> Color {
|
||||
return rgbt( r: r, g: g, b: b, t: 0 )
|
||||
}
|
||||
|
||||
public static func ==(lhs: Color, rhs: Color) -> Bool {
|
||||
return lhs.val == rhs.val
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
open class Drawable {
|
||||
|
||||
open let visible: Bool
|
||||
open let tag: [String]
|
||||
|
||||
public init(visible: Bool = true, tag: [String] = []) {
|
||||
self.visible = visible
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
|
||||
open let visible: Bool
|
||||
open let tag: [String]
|
||||
|
||||
public init(visible: Bool = true, tag: [String] = []) {
|
||||
self.visible = visible
|
||||
self.tag = tag
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
open class DropShadow: Effect {
|
||||
|
||||
open let radius: Double
|
||||
open let offset: Point
|
||||
open let color: Color
|
||||
open let input: Effect?
|
||||
|
||||
public init(radius: Double = 0, offset: Point = Point.origin, color: Color = Color.black, input: Effect? = nil) {
|
||||
self.radius = radius
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
self.input = input
|
||||
}
|
||||
|
||||
|
||||
open let radius: Double
|
||||
open let offset: Point
|
||||
open let color: Color
|
||||
open let input: Effect?
|
||||
|
||||
public init(radius: Double = 0, offset: Point = Point.origin, color: Color = Color.black, input: Effect? = nil) {
|
||||
self.radius = radius
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
self.input = input
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
open class Effect {
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
open class Font {
|
||||
|
||||
open let name: String
|
||||
open let size: Int
|
||||
|
||||
public init(name: String = "Serif", size: Int = 12) {
|
||||
self.name = name
|
||||
self.size = size
|
||||
}
|
||||
|
||||
|
||||
open let name: String
|
||||
open let size: Int
|
||||
|
||||
public init(name: String = "Serif", size: Int = 12) {
|
||||
self.name = name
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,4 @@ open class GaussianBlur: Effect {
|
||||
self.radius = radius
|
||||
self.input = input
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,5 +9,4 @@ open class Gradient: Fill {
|
||||
self.userSpace = userSpace
|
||||
self.stops = stops
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,48 +1,49 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class LinearGradient: Gradient {
|
||||
|
||||
open var x1: Double
|
||||
open var y1: Double
|
||||
open var x2: Double
|
||||
open var y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0, userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
super.init(
|
||||
userSpace: userSpace,
|
||||
stops: stops
|
||||
)
|
||||
}
|
||||
|
||||
public init(degree: Double = 0, from: Color, to: Color) {
|
||||
self.x1 = degree >= 135 && degree < 270 ? 1 : 0
|
||||
self.y1 = degree < 225 ? 0 : 1
|
||||
self.x2 = degree < 90 || degree >= 315 ? 1 : 0
|
||||
self.y2 = degree >= 45 && degree < 180 ? 1 : 0
|
||||
super.init(
|
||||
userSpace: false,
|
||||
stops: [Stop(offset: 0, color: from), Stop(offset: 1, color: to)]
|
||||
)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) {
|
||||
// TODO: - Check logic
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
|
||||
let point1 = CGPoint(x: x1, y: y1).applying(cgTransform)
|
||||
x1 = point1.x.doubleValue
|
||||
y1 = point1.y.doubleValue
|
||||
|
||||
let point2 = CGPoint(x: x2, y: y2).applying(cgTransform)
|
||||
x2 = point2.x.doubleValue
|
||||
y2 = point2.y.doubleValue
|
||||
}
|
||||
|
||||
open var x1: Double
|
||||
open var y1: Double
|
||||
open var x2: Double
|
||||
open var y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0, userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
super.init(
|
||||
userSpace: userSpace,
|
||||
stops: stops
|
||||
)
|
||||
}
|
||||
|
||||
public init(degree: Double = 0, from: Color, to: Color) {
|
||||
self.x1 = degree >= 135 && degree < 270 ? 1 : 0
|
||||
self.y1 = degree < 225 ? 0 : 1
|
||||
self.x2 = degree < 90 || degree >= 315 ? 1 : 0
|
||||
self.y2 = degree >= 45 && degree < 180 ? 1 : 0
|
||||
super.init(
|
||||
userSpace: false,
|
||||
stops: [Stop(offset: 0, color: from), Stop(offset: 1, color: to)]
|
||||
)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) {
|
||||
// TODO: - Check logic
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
|
||||
let point1 = CGPoint(x: x1, y: y1).applying(cgTransform)
|
||||
x1 = point1.x.doubleValue
|
||||
y1 = point1.y.doubleValue
|
||||
|
||||
let point2 = CGPoint(x: x2, y: y2).applying(cgTransform)
|
||||
x2 = point2.x.doubleValue
|
||||
y2 = point2.y.doubleValue
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,40 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class RadialGradient: Gradient {
|
||||
|
||||
open var cx: Double
|
||||
open var cy: Double
|
||||
open var fx: Double
|
||||
open var fy: Double
|
||||
open let r: Double
|
||||
|
||||
public init(cx: Double = 0.5, cy: Double = 0.5, fx: Double = 0.5, fy: Double = 0.5, r: Double = 0.5, userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.fx = fx
|
||||
self.fy = fy
|
||||
self.r = r
|
||||
super.init(
|
||||
userSpace: userSpace,
|
||||
stops: stops
|
||||
)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) {
|
||||
// TODO: - Check logic
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
|
||||
let point1 = CGPoint(x: cx, y: cy).applying(cgTransform)
|
||||
cx = point1.x.doubleValue
|
||||
cy = point1.y.doubleValue
|
||||
|
||||
let point2 = CGPoint(x: fx, y: fy).applying(cgTransform)
|
||||
fx = point2.x.doubleValue
|
||||
fy = point2.y.doubleValue
|
||||
}
|
||||
|
||||
open var cx: Double
|
||||
open var cy: Double
|
||||
open var fx: Double
|
||||
open var fy: Double
|
||||
open let r: Double
|
||||
|
||||
public init(cx: Double = 0.5, cy: Double = 0.5, fx: Double = 0.5, fy: Double = 0.5, r: Double = 0.5, userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.fx = fx
|
||||
self.fy = fy
|
||||
self.r = r
|
||||
super.init(
|
||||
userSpace: userSpace,
|
||||
stops: stops
|
||||
)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) {
|
||||
// TODO: - Check logic
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
|
||||
let point1 = CGPoint(x: cx, y: cy).applying(cgTransform)
|
||||
cx = point1.x.doubleValue
|
||||
cy = point1.y.doubleValue
|
||||
|
||||
let point2 = CGPoint(x: fx, y: fy).applying(cgTransform)
|
||||
fx = point2.x.doubleValue
|
||||
fy = point2.y.doubleValue
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,4 @@ open class Stop {
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,5 +15,4 @@ open class Stroke {
|
||||
self.join = join
|
||||
self.dashes = dashes
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,23 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
open class Arc: Locus {
|
||||
|
||||
open let ellipse: Ellipse
|
||||
open let shift: Double
|
||||
open let extent: Double
|
||||
|
||||
public init(ellipse: Ellipse, shift: Double = 0, extent: Double = 0) {
|
||||
self.ellipse = ellipse
|
||||
self.shift = shift
|
||||
self.extent = extent
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: ellipse.cx - ellipse.rx,
|
||||
y: ellipse.cy - ellipse.ry,
|
||||
w: ellipse.rx * 2.0,
|
||||
h: ellipse.ry * 2.0)
|
||||
}
|
||||
|
||||
|
||||
open let ellipse: Ellipse
|
||||
open let shift: Double
|
||||
open let extent: Double
|
||||
|
||||
public init(ellipse: Ellipse, shift: Double = 0, extent: Double = 0) {
|
||||
self.ellipse = ellipse
|
||||
self.shift = shift
|
||||
self.extent = extent
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: ellipse.cx - ellipse.rx,
|
||||
y: ellipse.cy - ellipse.ry,
|
||||
w: ellipse.rx * 2.0,
|
||||
h: ellipse.ry * 2.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
open class Circle: Locus {
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let r: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, r: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.r = r
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - r,
|
||||
y: cy - r,
|
||||
w: r * 2.0,
|
||||
h: r * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: Ellipse(cx: cx, cy: cy, rx: r, ry: r), shift: shift, extent: extent)
|
||||
}
|
||||
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let r: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, r: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.r = r
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - r,
|
||||
y: cy - r,
|
||||
w: r * 2.0,
|
||||
h: r * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: Ellipse(cx: cx, cy: cy, rx: r, ry: r), shift: shift, extent: extent)
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,29 @@
|
||||
import Foundation
|
||||
|
||||
open class Ellipse: Locus {
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, rx: Double = 0, ry: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - rx,
|
||||
y: cy - ry,
|
||||
w: rx * 2.0,
|
||||
h: ry * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: self, shift: shift, extent: extent)
|
||||
}
|
||||
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, rx: Double = 0, ry: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - rx,
|
||||
y: cy - ry,
|
||||
w: rx * 2.0,
|
||||
h: ry * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: self, shift: shift, extent: extent)
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,42 @@
|
||||
open class GeomUtils {
|
||||
|
||||
open class func concat(t1: Transform, t2: Transform) -> Transform {
|
||||
let nm11 = t2.m11 * t1.m11 + t2.m12 * t1.m21
|
||||
let nm21 = t2.m21 * t1.m11 + t2.m22 * t1.m21
|
||||
let ndx = t2.dx * t1.m11 + t2.dy * t1.m21 + t1.dx
|
||||
let nm12 = t2.m11 * t1.m12 + t2.m12 * t1.m22
|
||||
let nm22 = t2.m21 * t1.m12 + t2.m22 * t1.m22
|
||||
let ndy = t2.dx * t1.m12 + t2.dy * t1.m22 + t1.dy
|
||||
return Transform(m11: nm11, m12: nm12, m21: nm21, m22: nm22, dx: ndx, dy: ndy)
|
||||
}
|
||||
|
||||
open class func centerRotation(node: Node, place: Transform, angle: Double) -> Transform {
|
||||
|
||||
let center = GeomUtils.center(node: node)
|
||||
let move = Transform.move(dx: center.x, dy: center.y)
|
||||
|
||||
guard let moveInv = move.invert() else {
|
||||
return Transform()
|
||||
}
|
||||
|
||||
let r = Transform().rotate(angle: angle)
|
||||
|
||||
let moveAndRotate = GeomUtils.concat(t1: moveInv, t2: r)
|
||||
let returnToOrig = GeomUtils.concat(t1: moveAndRotate, t2: move)
|
||||
|
||||
return GeomUtils.concat(t1: returnToOrig, t2: place)
|
||||
}
|
||||
|
||||
open class func concat(t1: Transform, t2: Transform) -> Transform {
|
||||
let nm11 = t2.m11 * t1.m11 + t2.m12 * t1.m21
|
||||
let nm21 = t2.m21 * t1.m11 + t2.m22 * t1.m21
|
||||
let ndx = t2.dx * t1.m11 + t2.dy * t1.m21 + t1.dx
|
||||
let nm12 = t2.m11 * t1.m12 + t2.m12 * t1.m22
|
||||
let nm22 = t2.m21 * t1.m12 + t2.m22 * t1.m22
|
||||
let ndy = t2.dx * t1.m12 + t2.dy * t1.m22 + t1.dy
|
||||
return Transform(m11: nm11, m12: nm12, m21: nm21, m22: nm22, dx: ndx, dy: ndy)
|
||||
}
|
||||
|
||||
open class func centerRotation(node: Node, place: Transform, angle: Double) -> Transform {
|
||||
|
||||
open class func centerScale(node: Node, sx: Double, sy: Double) -> Transform {
|
||||
let center = GeomUtils.center(node: node)
|
||||
return Transform.move(dx: center.x * (1.0 - sx), dy: center.y * (1.0 - sy)).scale(sx: sx, sy: sy)
|
||||
let center = GeomUtils.center(node: node)
|
||||
let move = Transform.move(dx: center.x, dy: center.y)
|
||||
|
||||
guard let moveInv = move.invert() else {
|
||||
return Transform()
|
||||
}
|
||||
|
||||
open class func center(node: Node) -> Point {
|
||||
guard let bounds = node.bounds() else {
|
||||
return Point()
|
||||
}
|
||||
|
||||
return Point(x: bounds.x + bounds.w / 2.0, y: bounds.y + bounds.h / 2.0)
|
||||
let r = Transform().rotate(angle: angle)
|
||||
|
||||
let moveAndRotate = GeomUtils.concat(t1: moveInv, t2: r)
|
||||
let returnToOrig = GeomUtils.concat(t1: moveAndRotate, t2: move)
|
||||
|
||||
return GeomUtils.concat(t1: returnToOrig, t2: place)
|
||||
}
|
||||
|
||||
open class func centerScale(node: Node, sx: Double, sy: Double) -> Transform {
|
||||
let center = GeomUtils.center(node: node)
|
||||
return Transform.move(dx: center.x * (1.0 - sx), dy: center.y * (1.0 - sy)).scale(sx: sx, sy: sy)
|
||||
}
|
||||
|
||||
open class func center(node: Node) -> Point {
|
||||
guard let bounds = node.bounds() else {
|
||||
return Point()
|
||||
}
|
||||
|
||||
return Point(x: bounds.x + bounds.w / 2.0, y: bounds.y + bounds.h / 2.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
open class Insets {
|
||||
|
||||
open let top: Double
|
||||
open let right: Double
|
||||
open let bottom: Double
|
||||
open let left: Double
|
||||
|
||||
public init(top: Double = 0, right: Double = 0, bottom: Double = 0, left: Double = 0) {
|
||||
self.top = top
|
||||
self.right = right
|
||||
self.bottom = bottom
|
||||
self.left = left
|
||||
}
|
||||
|
||||
|
||||
open let top: Double
|
||||
open let right: Double
|
||||
open let bottom: Double
|
||||
open let left: Double
|
||||
|
||||
public init(top: Double = 0, right: Double = 0, bottom: Double = 0, left: Double = 0) {
|
||||
self.top = top
|
||||
self.right = right
|
||||
self.bottom = bottom
|
||||
self.left = left
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
import Foundation
|
||||
|
||||
open class Line: Locus {
|
||||
|
||||
open let x1: Double
|
||||
open let y1: Double
|
||||
open let x2: Double
|
||||
open let y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: min(x1, x2),
|
||||
y: min(y1, y2),
|
||||
w: abs(x1 - x2),
|
||||
h: abs(y1 - y2))
|
||||
}
|
||||
|
||||
open let x1: Double
|
||||
open let y1: Double
|
||||
open let x2: Double
|
||||
open let y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: min(x1, x2),
|
||||
y: min(y1, y2),
|
||||
w: abs(x1 - x2),
|
||||
h: abs(y1 - y2))
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
open class Locus {
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func bounds() -> Rect {
|
||||
return Rect()
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(with: Stroke) -> Shape {
|
||||
return Shape(form: self, stroke: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func fill(with: Fill) -> Shape {
|
||||
return Shape(form: self, fill: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape {
|
||||
return Shape(form: self, stroke: Stroke(fill: fill, width: width, cap: cap, join: join, dashes: dashes))
|
||||
}
|
||||
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func bounds() -> Rect {
|
||||
return Rect()
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(with: Stroke) -> Shape {
|
||||
return Shape(form: self, stroke: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func fill(with: Fill) -> Shape {
|
||||
return Shape(form: self, fill: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape {
|
||||
return Shape(form: self, stroke: Stroke(fill: fill, width: width, cap: cap, join: join, dashes: dashes))
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
open class MoveTo: PathBuilder {
|
||||
|
||||
// GENERATED NOT
|
||||
public init(x: Double, y: Double) {
|
||||
super.init(segment: PathSegment(type: .M, data: [x, y]))
|
||||
}
|
||||
|
||||
|
||||
// GENERATED NOT
|
||||
public init(x: Double, y: Double) {
|
||||
super.init(segment: PathSegment(type: .M, data: [x, y]))
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
open class Path: Locus {
|
||||
|
||||
open let segments: [PathSegment]
|
||||
|
||||
public init(segments: [PathSegment] = []) {
|
||||
self.segments = segments
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return pathBounds(self)!
|
||||
}
|
||||
|
||||
open let segments: [PathSegment]
|
||||
|
||||
public init(segments: [PathSegment] = []) {
|
||||
self.segments = segments
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return pathBounds(self)!
|
||||
}
|
||||
}
|
||||
|
@ -1,154 +1,153 @@
|
||||
import Foundation
|
||||
|
||||
open class PathBuilder {
|
||||
|
||||
open let segment: PathSegment
|
||||
open let rest: PathBuilder?
|
||||
|
||||
public init(segment: PathSegment, rest: PathBuilder? = nil) {
|
||||
self.segment = segment
|
||||
self.rest = rest
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func moveTo(x: Double, y: Double) -> PathBuilder {
|
||||
return M(x, y)
|
||||
|
||||
open let segment: PathSegment
|
||||
open let rest: PathBuilder?
|
||||
|
||||
public init(segment: PathSegment, rest: PathBuilder? = nil) {
|
||||
self.segment = segment
|
||||
self.rest = rest
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func moveTo(x: Double, y: Double) -> PathBuilder {
|
||||
return M(x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func lineTo(x: Double, y: Double) -> PathBuilder {
|
||||
return L(x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func cubicTo(x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) -> PathBuilder {
|
||||
return C(x1, y1, x2, y2, x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func quadraticTo(x1: Double, y1: Double, x: Double, y: Double) -> PathBuilder {
|
||||
return Q(x1, y1, x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arcTo(rx: Double, ry: Double, angle: Double, largeArc: Bool, sweep: Bool, x: Double, y: Double) -> PathBuilder {
|
||||
return A(rx, ry, angle, largeArc, sweep, x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func close() -> PathBuilder {
|
||||
return Z()
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func m(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .m, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func M(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .M, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func l(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .l, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func L(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .L, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func h(_ x: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .h, data: [x]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func H(_ x: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .H, data: [x]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func v(_ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .v, data: [y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func V(_ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .V, data: [y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func c(_ x1: Double, _ y1: Double, _ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .c, data: [x1, y1, x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func C(_ x1: Double, _ y1: Double, _ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .C, data: [x1, y1, x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func s(_ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .s, data: [x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func S(_ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .S, data: [x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func q(_ x1: Double, _ y1: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .q, data: [x1, y1, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func Q(_ x1: Double, _ y1: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .Q, data: [x1, y1, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func t(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .t, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func T(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .T, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func a(_ rx: Double, _ ry: Double, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .a, data: [rx, ry, angle, boolsToNum(largeArc, sweep: sweep), x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func A(_ rx: Double, _ ry: Double, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .A, data: [rx, ry, angle, boolsToNum(largeArc, sweep: sweep), x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func Z() -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .z), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func build() -> Path {
|
||||
var segments : [PathSegment] = []
|
||||
var builder : PathBuilder? = self
|
||||
while(builder != nil) {
|
||||
segments.append(builder!.segment)
|
||||
builder = builder!.rest
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func lineTo(x: Double, y: Double) -> PathBuilder {
|
||||
return L(x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func cubicTo(x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) -> PathBuilder {
|
||||
return C(x1, y1, x2, y2, x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func quadraticTo(x1: Double, y1: Double, x: Double, y: Double) -> PathBuilder {
|
||||
return Q(x1, y1, x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arcTo(rx: Double, ry: Double, angle: Double, largeArc: Bool, sweep: Bool, x: Double, y: Double) -> PathBuilder {
|
||||
return A(rx, ry, angle, largeArc, sweep, x, y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func close() -> PathBuilder {
|
||||
return Z()
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func m(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .m, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func M(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .M, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func l(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .l, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func L(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .L, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func h(_ x: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .h, data: [x]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func H(_ x: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .H, data: [x]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func v(_ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .v, data: [y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func V(_ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .V, data: [y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func c(_ x1: Double, _ y1: Double, _ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .c, data: [x1, y1, x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func C(_ x1: Double, _ y1: Double, _ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .C, data: [x1, y1, x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func s(_ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .s, data: [x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func S(_ x2: Double, _ y2: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .S, data: [x2, y2, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func q(_ x1: Double, _ y1: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .q, data: [x1, y1, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func Q(_ x1: Double, _ y1: Double, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .Q, data: [x1, y1, x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func t(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .t, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func T(_ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .T, data: [x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func a(_ rx: Double, _ ry: Double, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .a, data: [rx, ry, angle, boolsToNum(largeArc, sweep: sweep), x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func A(_ rx: Double, _ ry: Double, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ x: Double, _ y: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .A, data: [rx, ry, angle, boolsToNum(largeArc, sweep: sweep), x, y]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func Z() -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .z), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func build() -> Path {
|
||||
var segments : [PathSegment] = []
|
||||
var builder : PathBuilder? = self
|
||||
while(builder != nil) {
|
||||
segments.append(builder!.segment)
|
||||
builder = builder!.rest
|
||||
}
|
||||
return Path(segments: segments.reversed())
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
fileprivate func boolsToNum(_ largeArc: Bool, sweep: Bool) -> Double {
|
||||
return (largeArc ? 1 : 0) + (sweep ? 1 : 0) * 2;
|
||||
}
|
||||
|
||||
return Path(segments: segments.reversed())
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
fileprivate func boolsToNum(_ largeArc: Bool, sweep: Bool) -> Double {
|
||||
return (largeArc ? 1 : 0) + (sweep ? 1 : 0) * 2;
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
open class PathSegment {
|
||||
|
||||
open let type: PathSegmentType
|
||||
open let data: [Double]
|
||||
|
||||
public init(type: PathSegmentType = .M, data: [Double] = []) {
|
||||
self.type = type
|
||||
self.data = data
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func isAbsolute() -> Bool {
|
||||
switch type {
|
||||
case .M, .L, .H, .V, .C, .S, .Q, .T, .A:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
open let type: PathSegmentType
|
||||
open let data: [Double]
|
||||
|
||||
public init(type: PathSegmentType = .M, data: [Double] = []) {
|
||||
self.type = type
|
||||
self.data = data
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func isAbsolute() -> Bool {
|
||||
switch type {
|
||||
case .M, .L, .H, .V, .C, .S, .Q, .T, .A:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,29 @@
|
||||
import Foundation
|
||||
|
||||
open class Point: Locus {
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
|
||||
open static let origin: Point = Point( x: 0, y: 0 )
|
||||
|
||||
public init(x: Double = 0, y: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: x,
|
||||
y: y,
|
||||
w: 0.0,
|
||||
h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func add(_ point: Point) -> Point {
|
||||
return Point(
|
||||
x: self.x + point.x,
|
||||
y: self.y + point.y)
|
||||
}
|
||||
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
|
||||
open static let origin: Point = Point( x: 0, y: 0 )
|
||||
|
||||
public init(x: Double = 0, y: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: x,
|
||||
y: y,
|
||||
w: 0.0,
|
||||
h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func add(_ point: Point) -> Point {
|
||||
return Point(
|
||||
x: self.x + point.x,
|
||||
y: self.y + point.y)
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,46 @@
|
||||
import Foundation
|
||||
|
||||
open class Polygon: Locus {
|
||||
|
||||
open let points: [Double]
|
||||
|
||||
public init(points: [Double] = []) {
|
||||
self.points = points
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
guard !points.isEmpty else { return Rect.zero() }
|
||||
|
||||
open let points: [Double]
|
||||
var minX = Double(INT16_MAX)
|
||||
var minY = Double(INT16_MAX)
|
||||
var maxX = Double(INT16_MIN)
|
||||
var maxY = Double(INT16_MIN)
|
||||
|
||||
public init(points: [Double] = []) {
|
||||
self.points = points
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
guard !points.isEmpty else { return Rect.zero() }
|
||||
|
||||
var minX = Double(INT16_MAX)
|
||||
var minY = Double(INT16_MAX)
|
||||
var maxX = Double(INT16_MIN)
|
||||
var maxY = Double(INT16_MIN)
|
||||
|
||||
var isX = true
|
||||
for point in points {
|
||||
if isX {
|
||||
if minX > point {
|
||||
minX = point
|
||||
}
|
||||
|
||||
if maxX < point {
|
||||
maxX = point
|
||||
}
|
||||
} else {
|
||||
if minY > point {
|
||||
minY = point
|
||||
}
|
||||
|
||||
if maxY < point {
|
||||
maxY = point
|
||||
}
|
||||
}
|
||||
|
||||
isX = !isX
|
||||
var isX = true
|
||||
for point in points {
|
||||
if isX {
|
||||
if minX > point {
|
||||
minX = point
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
if maxX < point {
|
||||
maxX = point
|
||||
}
|
||||
} else {
|
||||
if minY > point {
|
||||
minY = point
|
||||
}
|
||||
|
||||
if maxY < point {
|
||||
maxY = point
|
||||
}
|
||||
}
|
||||
|
||||
isX = !isX
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,46 @@
|
||||
import Foundation
|
||||
|
||||
open class Polyline: Locus {
|
||||
|
||||
open let points: [Double]
|
||||
|
||||
public init(points: [Double] = []) {
|
||||
self.points = points
|
||||
}
|
||||
|
||||
open let points: [Double]
|
||||
|
||||
public init(points: [Double] = []) {
|
||||
self.points = points
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
guard !points.isEmpty else { return Rect.zero() }
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
guard !points.isEmpty else { return Rect.zero() }
|
||||
|
||||
var minX = Double(INT16_MAX)
|
||||
var minY = Double(INT16_MAX)
|
||||
var maxX = Double(INT16_MIN)
|
||||
var maxY = Double(INT16_MIN)
|
||||
|
||||
var isX = true
|
||||
for point in points {
|
||||
if isX {
|
||||
if minX > point {
|
||||
minX = point
|
||||
}
|
||||
|
||||
if maxX < point {
|
||||
maxX = point
|
||||
}
|
||||
} else {
|
||||
if minY > point {
|
||||
minY = point
|
||||
}
|
||||
|
||||
if maxY < point {
|
||||
maxY = point
|
||||
}
|
||||
}
|
||||
|
||||
isX = !isX
|
||||
var minX = Double(INT16_MAX)
|
||||
var minY = Double(INT16_MAX)
|
||||
var maxX = Double(INT16_MIN)
|
||||
var maxY = Double(INT16_MIN)
|
||||
|
||||
var isX = true
|
||||
for point in points {
|
||||
if isX {
|
||||
if minX > point {
|
||||
minX = point
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
if maxX < point {
|
||||
maxX = point
|
||||
}
|
||||
} else {
|
||||
if minY > point {
|
||||
minY = point
|
||||
}
|
||||
|
||||
if maxY < point {
|
||||
maxY = point
|
||||
}
|
||||
}
|
||||
|
||||
isX = !isX
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +1,68 @@
|
||||
import Foundation
|
||||
|
||||
open class Rect: Locus {
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(x: Double = 0, y: Double = 0, w: Double = 0, h: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func round(rx: Double, ry: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: rx, ry: ry)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func round(r: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: r, ry: r)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func contains(locus: Locus) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
class func zero() -> Rect {
|
||||
return Rect(x: 0.0, y: 0.0, w: 0.0, h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func move(offset: Point) -> Rect {
|
||||
return Rect(
|
||||
x: self.x + offset.x,
|
||||
y: self.y + offset.y,
|
||||
w: self.w,
|
||||
h: self.h)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func union(rect: Rect) -> Rect {
|
||||
return Rect(
|
||||
x: min(self.x, rect.x),
|
||||
y: min(self.y, rect.y),
|
||||
w: max(self.x + self.w, rect.x + rect.w) - min(self.x, rect.x),
|
||||
h: max(self.y + self.h, rect.y + rect.h) - min(self.y, rect.y))
|
||||
}
|
||||
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(x: Double = 0, y: Double = 0, w: Double = 0, h: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func round(rx: Double, ry: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: rx, ry: ry)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func round(r: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: r, ry: r)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func contains(locus: Locus) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
class func zero() -> Rect {
|
||||
return Rect(x: 0.0, y: 0.0, w: 0.0, h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func move(offset: Point) -> Rect {
|
||||
return Rect(
|
||||
x: self.x + offset.x,
|
||||
y: self.y + offset.y,
|
||||
w: self.w,
|
||||
h: self.h)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func union(rect: Rect) -> Rect {
|
||||
return Rect(
|
||||
x: min(self.x, rect.x),
|
||||
y: min(self.y, rect.y),
|
||||
w: max(self.x + self.w, rect.x + rect.w) - min(self.x, rect.x),
|
||||
h: max(self.y + self.h, rect.y + rect.h) - min(self.y, rect.y))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Rect {
|
||||
public static func ==(lhs: Rect, rhs: Rect) -> Bool {
|
||||
return lhs.x == rhs.x
|
||||
&& lhs.y == rhs.y
|
||||
&& lhs.w == rhs.w
|
||||
&& lhs.h == rhs.h
|
||||
}
|
||||
public static func ==(lhs: Rect, rhs: Rect) -> Bool {
|
||||
return lhs.x == rhs.x
|
||||
&& lhs.y == rhs.y
|
||||
&& lhs.w == rhs.w
|
||||
&& lhs.h == rhs.h
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
open class RoundRect: Locus {
|
||||
|
||||
open let rect: Rect
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(rect: Rect, rx: Double = 0, ry: Double = 0) {
|
||||
self.rect = rect
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return rect
|
||||
}
|
||||
|
||||
open let rect: Rect
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(rect: Rect, rx: Double = 0, ry: Double = 0) {
|
||||
self.rect = rect
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return rect
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
open class Size {
|
||||
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(w: Double = 0, h: Double = 0) {
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
|
||||
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(w: Double = 0, h: Double = 0) {
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
}
|
||||
|
@ -1,89 +1,88 @@
|
||||
import Foundation
|
||||
|
||||
public final class Transform {
|
||||
|
||||
public let m11: Double
|
||||
public let m12: Double
|
||||
public let m21: Double
|
||||
public let m22: Double
|
||||
public let dx: Double
|
||||
public let dy: Double
|
||||
|
||||
public static let identity: Transform = Transform()
|
||||
|
||||
public init(m11: Double = 1, m12: Double = 0, m21: Double = 0, m22: Double = 1, dx: Double = 0, dy: Double = 0) {
|
||||
self.m11 = m11
|
||||
self.m12 = m12
|
||||
self.m21 = m21
|
||||
self.m22 = m22
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func move(dx: Double, dy: Double) -> Transform {
|
||||
return Transform(m11: m11, m12: m12, m21: m21, m22: m22,
|
||||
dx: dx * m11 + dy * m21 + self.dx, dy: dx * m12 + dy * m22 + self.dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func scale(sx: Double, sy: Double) -> Transform {
|
||||
return Transform(m11: m11 * sx, m12: m12 * sx, m21: m21 * sy, m22: m22 * sy, dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func shear(shx: Double, shy: Double) -> Transform {
|
||||
return Transform(m11: m11 + m21 * shy, m12: m12 + m22 * shy,
|
||||
m21: m11 * shx + m21, m22: m12 * shx + m22, dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func rotate(angle: Double) -> Transform {
|
||||
let asin = sin(angle); let acos = cos(angle)
|
||||
return Transform(m11: acos * m11 + asin * m21, m12: acos * m12 + asin * m22,
|
||||
m21: -asin * m11 + acos * m21, m22: -asin * m12 + acos * m22, dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func rotate(angle: Double, x: Double, y: Double) -> Transform {
|
||||
return move(dx: x, dy: y).rotate(angle: angle).move(dx: -x, dy: -y)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public class func move(dx: Double, dy: Double) -> Transform {
|
||||
return Transform(dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public class func scale(sx: Double, sy: Double) -> Transform {
|
||||
return Transform(m11: sx, m22: sy)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public class func shear(shx: Double, shy: Double) -> Transform {
|
||||
return Transform(m12: shy, m21: shx)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public class func rotate(angle: Double) -> Transform {
|
||||
let asin = sin(angle); let acos = cos(angle)
|
||||
return Transform(m11: acos, m12: asin, m21: -asin, m22: acos)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public class func rotate(angle: Double, x: Double, y: Double) -> Transform {
|
||||
return Transform.move(dx: x, dy: y).rotate(angle: angle).move(dx: -x, dy: -y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func invert() -> Transform? {
|
||||
let det = self.m11 * self.m22 - self.m12 * self.m21
|
||||
if (det == 0) {
|
||||
return nil
|
||||
}
|
||||
return Transform(m11: m22 / det, m12: -m12 / det, m21: -m21 / det, m22: m11 / det,
|
||||
dx: (m21 * dy - m22 * dx) / det,
|
||||
dy: (m12 * dx - m11 * dy) / det)
|
||||
}
|
||||
|
||||
|
||||
public let m11: Double
|
||||
public let m12: Double
|
||||
public let m21: Double
|
||||
public let m22: Double
|
||||
public let dx: Double
|
||||
public let dy: Double
|
||||
|
||||
public static let identity: Transform = Transform()
|
||||
|
||||
public init(m11: Double = 1, m12: Double = 0, m21: Double = 0, m22: Double = 1, dx: Double = 0, dy: Double = 0) {
|
||||
self.m11 = m11
|
||||
self.m12 = m12
|
||||
self.m21 = m21
|
||||
self.m22 = m22
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func move(dx: Double, dy: Double) -> Transform {
|
||||
return Transform(m11: m11, m12: m12, m21: m21, m22: m22,
|
||||
dx: dx * m11 + dy * m21 + self.dx, dy: dx * m12 + dy * m22 + self.dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func scale(sx: Double, sy: Double) -> Transform {
|
||||
return Transform(m11: m11 * sx, m12: m12 * sx, m21: m21 * sy, m22: m22 * sy, dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func shear(shx: Double, shy: Double) -> Transform {
|
||||
return Transform(m11: m11 + m21 * shy, m12: m12 + m22 * shy,
|
||||
m21: m11 * shx + m21, m22: m12 * shx + m22, dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func rotate(angle: Double) -> Transform {
|
||||
let asin = sin(angle); let acos = cos(angle)
|
||||
return Transform(m11: acos * m11 + asin * m21, m12: acos * m12 + asin * m22,
|
||||
m21: -asin * m11 + acos * m21, m22: -asin * m12 + acos * m22, dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func rotate(angle: Double, x: Double, y: Double) -> Transform {
|
||||
return move(dx: x, dy: y).rotate(angle: angle).move(dx: -x, dy: -y)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public class func move(dx: Double, dy: Double) -> Transform {
|
||||
return Transform(dx: dx, dy: dy)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public class func scale(sx: Double, sy: Double) -> Transform {
|
||||
return Transform(m11: sx, m22: sy)
|
||||
}
|
||||
|
||||
// GENERATED
|
||||
public class func shear(shx: Double, shy: Double) -> Transform {
|
||||
return Transform(m12: shy, m21: shx)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public class func rotate(angle: Double) -> Transform {
|
||||
let asin = sin(angle); let acos = cos(angle)
|
||||
return Transform(m11: acos, m12: asin, m21: -asin, m22: acos)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public class func rotate(angle: Double, x: Double, y: Double) -> Transform {
|
||||
return Transform.move(dx: x, dy: y).rotate(angle: angle).move(dx: -x, dy: -y)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func invert() -> Transform? {
|
||||
let det = self.m11 * self.m22 - self.m12 * self.m21
|
||||
if (det == 0) {
|
||||
return nil
|
||||
}
|
||||
return Transform(m11: m22 / det, m12: -m12 / det, m21: -m21 / det, m22: m11 / det,
|
||||
dx: (m21 * dy - m22 * dx) / det,
|
||||
dy: (m12 * dx - m11 * dy) / det)
|
||||
}
|
||||
}
|
||||
|
@ -1,152 +1,152 @@
|
||||
import Foundation
|
||||
|
||||
open class Group: Node {
|
||||
|
||||
open var contentsVar: AnimatableVariable<[Node]>
|
||||
open var contents: [Node] {
|
||||
get { return contentsVar.value }
|
||||
set(val) {
|
||||
contentsVar.value = val
|
||||
|
||||
if let view = nodesMap.getView(self) {
|
||||
val.forEach { subNode in
|
||||
nodesMap.add(subNode, view: view)
|
||||
}
|
||||
}
|
||||
|
||||
val.forEach { subNode in
|
||||
nodesMap.add(subNode, parent: self)
|
||||
}
|
||||
|
||||
open var contentsVar: AnimatableVariable<[Node]>
|
||||
open var contents: [Node] {
|
||||
get { return contentsVar.value }
|
||||
set(val) {
|
||||
contentsVar.value = val
|
||||
|
||||
if let view = nodesMap.getView(self) {
|
||||
val.forEach { subNode in
|
||||
nodesMap.add(subNode, view: view)
|
||||
}
|
||||
}
|
||||
|
||||
val.forEach { subNode in
|
||||
nodesMap.add(subNode, parent: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public init(contents: [Node] = [], place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.contentsVar = AnimatableVariable<[Node]>(contents)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
|
||||
self.contentsVar.node = self
|
||||
}
|
||||
|
||||
// Searching
|
||||
|
||||
override public func nodeBy(tag: String) -> Node? {
|
||||
if let node = super.nodeBy(tag: tag) {
|
||||
return node
|
||||
}
|
||||
|
||||
|
||||
public init(contents: [Node] = [], place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.contentsVar = AnimatableVariable<[Node]>(contents)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
|
||||
self.contentsVar.node = self
|
||||
for child in contents {
|
||||
if let node = child.nodeBy(tag: tag) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
// Searching
|
||||
return .none
|
||||
}
|
||||
|
||||
override public func nodesBy(tag: String) -> [Node] {
|
||||
|
||||
override public func nodeBy(tag: String) -> Node? {
|
||||
if let node = super.nodeBy(tag: tag) {
|
||||
return node
|
||||
}
|
||||
|
||||
for child in contents {
|
||||
if let node = child.nodeBy(tag: tag) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
return .none
|
||||
var result = [Node]()
|
||||
contents.forEach { child in
|
||||
result.append(contentsOf: child.nodesBy(tag: tag))
|
||||
}
|
||||
|
||||
override public func nodesBy(tag: String) -> [Node] {
|
||||
|
||||
var result = [Node]()
|
||||
contents.forEach { child in
|
||||
result.append(contentsOf: child.nodesBy(tag: tag))
|
||||
}
|
||||
|
||||
if let node = super.nodeBy(tag: tag) {
|
||||
result.append(node)
|
||||
}
|
||||
|
||||
return result
|
||||
if let node = super.nodeBy(tag: tag) {
|
||||
result.append(node)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
override internal func bounds() -> Rect? {
|
||||
var union: Rect?
|
||||
|
||||
contents.forEach { node in
|
||||
guard let nodeBounds = node.bounds()?.applyTransform(node.place) else {
|
||||
return
|
||||
}
|
||||
|
||||
union = union?.union(rect: nodeBounds) ?? nodeBounds
|
||||
}
|
||||
|
||||
return union
|
||||
return result
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
override internal func bounds() -> Rect? {
|
||||
var union: Rect?
|
||||
|
||||
contents.forEach { node in
|
||||
guard let nodeBounds = node.bounds()?.applyTransform(node.place) else {
|
||||
return
|
||||
}
|
||||
|
||||
union = union?.union(rect: nodeBounds) ?? nodeBounds
|
||||
}
|
||||
|
||||
override func shouldCheckForPressed() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForPressed()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForPressed()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return union
|
||||
}
|
||||
|
||||
override func shouldCheckForPressed() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForPressed()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForPressed()
|
||||
}
|
||||
|
||||
override func shouldCheckForMoved() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForMoved()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForMoved()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return shouldCheck
|
||||
}
|
||||
|
||||
override func shouldCheckForMoved() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForMoved()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForMoved()
|
||||
}
|
||||
|
||||
override func shouldCheckForReleased() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForReleased()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForReleased()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return shouldCheck
|
||||
}
|
||||
|
||||
override func shouldCheckForReleased() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForReleased()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForReleased()
|
||||
}
|
||||
|
||||
override func shouldCheckForTap() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForTap()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForTap()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return shouldCheck
|
||||
}
|
||||
|
||||
override func shouldCheckForTap() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForTap()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForTap()
|
||||
}
|
||||
|
||||
override func shouldCheckForPan() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForPan()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForPan()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return shouldCheck
|
||||
}
|
||||
|
||||
override func shouldCheckForPan() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForPan()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForPan()
|
||||
}
|
||||
|
||||
override func shouldCheckForPinch() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForPinch()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForPinch()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return shouldCheck
|
||||
}
|
||||
|
||||
override func shouldCheckForPinch() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForPinch()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForPinch()
|
||||
}
|
||||
|
||||
override func shouldCheckForRotate() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForRotate()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForRotate()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
return shouldCheck
|
||||
}
|
||||
|
||||
override func shouldCheckForRotate() -> Bool {
|
||||
var shouldCheck = super.shouldCheckForRotate()
|
||||
contents.forEach { node in
|
||||
shouldCheck = shouldCheck || node.shouldCheckForRotate()
|
||||
}
|
||||
|
||||
return shouldCheck
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array where Element: Node {
|
||||
public func group( place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) -> Group {
|
||||
return Group(contents: self, place: place, opaque: opaque, opacity: opacity, clip: clip, effect: effect, visible: visible, tag: tag)
|
||||
}
|
||||
public func group( place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) -> Group {
|
||||
return Group(contents: self, place: place, opaque: opaque, opacity: opacity, clip: clip, effect: effect, visible: visible, tag: tag)
|
||||
}
|
||||
}
|
||||
|
@ -1,135 +1,137 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class Image: Node {
|
||||
|
||||
open let srcVar: Variable<String>
|
||||
open var src: String {
|
||||
get { return srcVar.value }
|
||||
set(val) { srcVar.value = val }
|
||||
}
|
||||
|
||||
open let xAlignVar: Variable<Align>
|
||||
open var xAlign: Align {
|
||||
get { return xAlignVar.value }
|
||||
set(val) { xAlignVar.value = val }
|
||||
}
|
||||
|
||||
open let yAlignVar: Variable<Align>
|
||||
open var yAlign: Align {
|
||||
get { return yAlignVar.value }
|
||||
set(val) { yAlignVar.value = val }
|
||||
}
|
||||
|
||||
open let aspectRatioVar: Variable<AspectRatio>
|
||||
open var aspectRatio: AspectRatio {
|
||||
get { return aspectRatioVar.value }
|
||||
set(val) { aspectRatioVar.value = val }
|
||||
}
|
||||
|
||||
open let wVar: Variable<Int>
|
||||
open var w: Int {
|
||||
get { return wVar.value }
|
||||
set(val) { wVar.value = val }
|
||||
}
|
||||
|
||||
open let hVar: Variable<Int>
|
||||
open var h: Int {
|
||||
get { return hVar.value }
|
||||
set(val) { hVar.value = val }
|
||||
}
|
||||
|
||||
private var mImage: MImage?
|
||||
|
||||
public init(src: String, xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.srcVar = Variable<String>(src)
|
||||
self.xAlignVar = Variable<Align>(xAlign)
|
||||
self.yAlignVar = Variable<Align>(yAlign)
|
||||
self.aspectRatioVar = Variable<AspectRatio>(aspectRatio)
|
||||
self.wVar = Variable<Int>(w)
|
||||
self.hVar = Variable<Int>(h)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
}
|
||||
|
||||
public init(image: MImage, xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
|
||||
open let srcVar: Variable<String>
|
||||
open var src: String {
|
||||
get { return srcVar.value }
|
||||
set(val) { srcVar.value = val }
|
||||
var oldId: String?
|
||||
for key in imagesMap.keys {
|
||||
if image === imagesMap[key] {
|
||||
oldId = key
|
||||
}
|
||||
}
|
||||
|
||||
open let xAlignVar: Variable<Align>
|
||||
open var xAlign: Align {
|
||||
get { return xAlignVar.value }
|
||||
set(val) { xAlignVar.value = val }
|
||||
let id = oldId ?? UUID().uuidString
|
||||
imagesMap[id] = image
|
||||
|
||||
self.srcVar = Variable<String>("memory://\(id)")
|
||||
self.xAlignVar = Variable<Align>(xAlign)
|
||||
self.yAlignVar = Variable<Align>(yAlign)
|
||||
self.aspectRatioVar = Variable<AspectRatio>(aspectRatio)
|
||||
self.wVar = Variable<Int>(w)
|
||||
self.hVar = Variable<Int>(h)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
}
|
||||
|
||||
override func bounds() -> Rect? {
|
||||
if w != 0 && h != 0 {
|
||||
return Rect(x: 0.0, y: 0.0, w: Double(w), h: Double(h))
|
||||
}
|
||||
|
||||
open let yAlignVar: Variable<Align>
|
||||
open var yAlign: Align {
|
||||
get { return yAlignVar.value }
|
||||
set(val) { yAlignVar.value = val }
|
||||
mImage = image()
|
||||
|
||||
guard let mImage = mImage else {
|
||||
return .none
|
||||
}
|
||||
|
||||
open let aspectRatioVar: Variable<AspectRatio>
|
||||
open var aspectRatio: AspectRatio {
|
||||
get { return aspectRatioVar.value }
|
||||
set(val) { aspectRatioVar.value = val }
|
||||
return Rect(x: 0.0, y: 0.0,
|
||||
w: Double(mImage.size.width),
|
||||
h: Double(mImage.size.height))
|
||||
|
||||
}
|
||||
|
||||
func image() -> MImage? {
|
||||
|
||||
// image already loaded
|
||||
if let _ = mImage {
|
||||
return mImage
|
||||
}
|
||||
|
||||
open let wVar: Variable<Int>
|
||||
open var w: Int {
|
||||
get { return wVar.value }
|
||||
set(val) { wVar.value = val }
|
||||
// In-memory image
|
||||
if src.contains("memory") {
|
||||
let id = src.replacingOccurrences(of: "memory://", with: "")
|
||||
return imagesMap[id]
|
||||
}
|
||||
|
||||
open let hVar: Variable<Int>
|
||||
open var h: Int {
|
||||
get { return hVar.value }
|
||||
set(val) { hVar.value = val }
|
||||
// Base64 image
|
||||
if src.hasPrefix("data:image/png;base64,") {
|
||||
src = src.replacingOccurrences(of: "data:image/png;base64,", with: "")
|
||||
guard let decodedData = Data(base64Encoded: src, options: .ignoreUnknownCharacters) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return MImage(data: decodedData)
|
||||
}
|
||||
|
||||
private var uiImage: UIImage?
|
||||
|
||||
public init(src: String, xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.srcVar = Variable<String>(src)
|
||||
self.xAlignVar = Variable<Align>(xAlign)
|
||||
self.yAlignVar = Variable<Align>(yAlign)
|
||||
self.aspectRatioVar = Variable<AspectRatio>(aspectRatio)
|
||||
self.wVar = Variable<Int>(w)
|
||||
self.hVar = Variable<Int>(h)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
}
|
||||
|
||||
public init(image: UIImage, xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
|
||||
var oldId: String?
|
||||
for key in imagesMap.keys {
|
||||
if image === imagesMap[key] {
|
||||
oldId = key
|
||||
}
|
||||
}
|
||||
|
||||
let id = oldId ?? UUID().uuidString
|
||||
imagesMap[id] = image
|
||||
|
||||
self.srcVar = Variable<String>("memory://\(id)")
|
||||
self.xAlignVar = Variable<Align>(xAlign)
|
||||
self.yAlignVar = Variable<Align>(yAlign)
|
||||
self.aspectRatioVar = Variable<AspectRatio>(aspectRatio)
|
||||
self.wVar = Variable<Int>(w)
|
||||
self.hVar = Variable<Int>(h)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
}
|
||||
|
||||
override func bounds() -> Rect? {
|
||||
if w != 0 && h != 0 {
|
||||
return Rect(x: 0.0, y: 0.0, w: Double(w), h: Double(h))
|
||||
}
|
||||
|
||||
uiImage = image()
|
||||
|
||||
guard let uiImage = uiImage else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return Rect(x: 0.0, y: 0.0,
|
||||
w: Double(uiImage.size.width),
|
||||
h: Double(uiImage.size.height))
|
||||
|
||||
}
|
||||
|
||||
func image() -> UIImage? {
|
||||
|
||||
// image already loaded
|
||||
if let _ = uiImage {
|
||||
return uiImage
|
||||
}
|
||||
|
||||
// In-memory image
|
||||
if src.contains("memory") {
|
||||
let id = src.replacingOccurrences(of: "memory://", with: "")
|
||||
return imagesMap[id]
|
||||
}
|
||||
|
||||
// Base64 image
|
||||
if src.hasPrefix("data:image/png;base64,") {
|
||||
src = src.replacingOccurrences(of: "data:image/png;base64,", with: "")
|
||||
guard let decodedData = Data(base64Encoded: src, options: .ignoreUnknownCharacters) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return UIImage(data: decodedData)
|
||||
}
|
||||
|
||||
// General case
|
||||
return UIImage(named: src)
|
||||
}
|
||||
// General case
|
||||
return MImage(named: src)
|
||||
}
|
||||
}
|
||||
|
@ -1,230 +1,229 @@
|
||||
import Foundation
|
||||
|
||||
open class Node: Drawable {
|
||||
|
||||
open let placeVar: AnimatableVariable<Transform>
|
||||
open var place: Transform {
|
||||
get { return placeVar.value }
|
||||
set(val) { placeVar.value = val }
|
||||
}
|
||||
|
||||
open let opaqueVar: Variable<Bool>
|
||||
open var opaque: Bool {
|
||||
get { return opaqueVar.value }
|
||||
set(val) { opaqueVar.value = val }
|
||||
}
|
||||
|
||||
open let opacityVar: AnimatableVariable<Double>
|
||||
open var opacity: Double {
|
||||
get { return opacityVar.value }
|
||||
set(val) { opacityVar.value = val }
|
||||
}
|
||||
|
||||
open let clipVar: Variable<Locus?>
|
||||
open var clip: Locus? {
|
||||
get { return clipVar.value }
|
||||
set(val) { clipVar.value = val }
|
||||
}
|
||||
|
||||
open let effectVar: Variable<Effect?>
|
||||
open var effect: Effect? {
|
||||
get { return effectVar.value }
|
||||
set(val) { effectVar.value = val }
|
||||
}
|
||||
|
||||
// MARK: - Searching
|
||||
public func nodeBy(tag: String) -> Node? {
|
||||
if self.tag.contains(tag) {
|
||||
return self
|
||||
}
|
||||
|
||||
return .none
|
||||
|
||||
open let placeVar: AnimatableVariable<Transform>
|
||||
open var place: Transform {
|
||||
get { return placeVar.value }
|
||||
set(val) { placeVar.value = val }
|
||||
}
|
||||
|
||||
open let opaqueVar: Variable<Bool>
|
||||
open var opaque: Bool {
|
||||
get { return opaqueVar.value }
|
||||
set(val) { opaqueVar.value = val }
|
||||
}
|
||||
|
||||
open let opacityVar: AnimatableVariable<Double>
|
||||
open var opacity: Double {
|
||||
get { return opacityVar.value }
|
||||
set(val) { opacityVar.value = val }
|
||||
}
|
||||
|
||||
open let clipVar: Variable<Locus?>
|
||||
open var clip: Locus? {
|
||||
get { return clipVar.value }
|
||||
set(val) { clipVar.value = val }
|
||||
}
|
||||
|
||||
open let effectVar: Variable<Effect?>
|
||||
open var effect: Effect? {
|
||||
get { return effectVar.value }
|
||||
set(val) { effectVar.value = val }
|
||||
}
|
||||
|
||||
// MARK: - Searching
|
||||
public func nodeBy(tag: String) -> Node? {
|
||||
if self.tag.contains(tag) {
|
||||
return self
|
||||
}
|
||||
|
||||
public func nodesBy(tag: String) -> [Node] {
|
||||
return [nodeBy(tag: tag)].flatMap { $0 }
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
public func nodesBy(tag: String) -> [Node] {
|
||||
return [nodeBy(tag: tag)].flatMap { $0 }
|
||||
}
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
var touchPressedHandlers = [ChangeHandler<TouchEvent>]()
|
||||
var touchMovedHandlers = [ChangeHandler<TouchEvent>]()
|
||||
var touchReleasedHandlers = [ChangeHandler<TouchEvent>]()
|
||||
|
||||
var tapHandlers = [ChangeHandler<TapEvent>]()
|
||||
var panHandlers = [ChangeHandler<PanEvent>]()
|
||||
var rotateHandlers = [ChangeHandler<RotateEvent>]()
|
||||
var pinchHandlers = [ChangeHandler<PinchEvent>]()
|
||||
|
||||
@discardableResult public func onTouchPressed (_ f: @escaping (TouchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchPressedHandlers.append(handler)
|
||||
|
||||
// MARK: - Events
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.touchPressedHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.touchPressedHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onTouchMoved (_ f: @escaping (TouchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchMovedHandlers.append(handler)
|
||||
|
||||
var touchPressedHandlers = [ChangeHandler<TouchEvent>]()
|
||||
var touchMovedHandlers = [ChangeHandler<TouchEvent>]()
|
||||
var touchReleasedHandlers = [ChangeHandler<TouchEvent>]()
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.touchMovedHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.touchMovedHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onTouchReleased(_ f: @escaping (TouchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchReleasedHandlers.append(handler)
|
||||
|
||||
var tapHandlers = [ChangeHandler<TapEvent>]()
|
||||
var panHandlers = [ChangeHandler<PanEvent>]()
|
||||
var rotateHandlers = [ChangeHandler<RotateEvent>]()
|
||||
var pinchHandlers = [ChangeHandler<PinchEvent>]()
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.touchReleasedHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.touchReleasedHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onTap(_ f: @escaping (TapEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TapEvent>(f)
|
||||
tapHandlers.append(handler)
|
||||
|
||||
@discardableResult public func onTouchPressed (_ f: @escaping (TouchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchPressedHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.touchPressedHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.touchPressedHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.tapHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.tapHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onPan(_ f: @escaping (PanEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<PanEvent>(f)
|
||||
panHandlers.append(handler)
|
||||
|
||||
@discardableResult public func onTouchMoved (_ f: @escaping (TouchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchMovedHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.touchMovedHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.touchMovedHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.panHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.panHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onRotate(_ f: @escaping (RotateEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<RotateEvent>(f)
|
||||
rotateHandlers.append(handler)
|
||||
|
||||
@discardableResult public func onTouchReleased(_ f: @escaping (TouchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchReleasedHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.touchReleasedHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.touchReleasedHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.rotateHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.rotateHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onPinch(_ f: @escaping (PinchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<PinchEvent>(f)
|
||||
pinchHandlers.append(handler)
|
||||
|
||||
@discardableResult public func onTap(_ f: @escaping (TapEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<TapEvent>(f)
|
||||
tapHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.tapHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.tapHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onPan(_ f: @escaping (PanEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<PanEvent>(f)
|
||||
panHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.panHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.panHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onRotate(_ f: @escaping (RotateEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<RotateEvent>(f)
|
||||
rotateHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.rotateHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.rotateHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult public func onPinch(_ f: @escaping (PinchEvent) -> ()) -> Disposable {
|
||||
let handler = ChangeHandler<PinchEvent>(f)
|
||||
pinchHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.pinchHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.pinchHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func handleTouchPressed(_ event: TouchEvent) {
|
||||
touchPressedHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleTouchReleased(_ event: TouchEvent) {
|
||||
touchReleasedHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleTouchMoved(_ event: TouchEvent) {
|
||||
touchMovedHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleTap( _ event: TapEvent ) {
|
||||
tapHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handlePan( _ event: PanEvent ) {
|
||||
panHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleRotate( _ event: RotateEvent ) {
|
||||
rotateHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handlePinch( _ event: PinchEvent ) {
|
||||
pinchHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func shouldCheckForPressed() -> Bool {
|
||||
return touchPressedHandlers.count > 0
|
||||
}
|
||||
|
||||
func shouldCheckForMoved() -> Bool {
|
||||
return touchMovedHandlers.count > 0
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForReleased() -> Bool {
|
||||
return touchReleasedHandlers.count > 0
|
||||
}
|
||||
|
||||
func shouldCheckForTap() -> Bool {
|
||||
return tapHandlers.count > 0
|
||||
}
|
||||
|
||||
func shouldCheckForPan() -> Bool {
|
||||
return panHandlers.count > 0
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForRotate() -> Bool {
|
||||
return rotateHandlers.count > 0
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForPinch() -> Bool {
|
||||
return pinchHandlers.count > 0
|
||||
}
|
||||
|
||||
public init(place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.placeVar = AnimatableVariable<Transform>(place)
|
||||
self.opaqueVar = Variable<Bool>(opaque)
|
||||
self.opacityVar = AnimatableVariable<Double>(opacity)
|
||||
self.clipVar = Variable<Locus?>(clip)
|
||||
self.effectVar = Variable<Effect?>(effect)
|
||||
super.init(
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
self.placeVar.node = self
|
||||
self.opacityVar.node = self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
internal func bounds() -> Rect? {
|
||||
return Rect()
|
||||
}
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.pinchHandlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.pinchHandlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func handleTouchPressed(_ event: TouchEvent) {
|
||||
touchPressedHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleTouchReleased(_ event: TouchEvent) {
|
||||
touchReleasedHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleTouchMoved(_ event: TouchEvent) {
|
||||
touchMovedHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleTap( _ event: TapEvent ) {
|
||||
tapHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handlePan( _ event: PanEvent ) {
|
||||
panHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handleRotate( _ event: RotateEvent ) {
|
||||
rotateHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func handlePinch( _ event: PinchEvent ) {
|
||||
pinchHandlers.forEach { handler in handler.handle(event) }
|
||||
}
|
||||
|
||||
func shouldCheckForPressed() -> Bool {
|
||||
return touchPressedHandlers.count > 0
|
||||
}
|
||||
|
||||
func shouldCheckForMoved() -> Bool {
|
||||
return touchMovedHandlers.count > 0
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForReleased() -> Bool {
|
||||
return touchReleasedHandlers.count > 0
|
||||
}
|
||||
|
||||
func shouldCheckForTap() -> Bool {
|
||||
return tapHandlers.count > 0
|
||||
}
|
||||
|
||||
func shouldCheckForPan() -> Bool {
|
||||
return panHandlers.count > 0
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForRotate() -> Bool {
|
||||
return rotateHandlers.count > 0
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForPinch() -> Bool {
|
||||
return pinchHandlers.count > 0
|
||||
}
|
||||
|
||||
public init(place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.placeVar = AnimatableVariable<Transform>(place)
|
||||
self.opaqueVar = Variable<Bool>(opaque)
|
||||
self.opacityVar = AnimatableVariable<Double>(opacity)
|
||||
self.clipVar = Variable<Locus?>(clip)
|
||||
self.effectVar = Variable<Effect?>(effect)
|
||||
super.init(
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
self.placeVar.node = self
|
||||
self.opacityVar.node = self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
internal func bounds() -> Rect? {
|
||||
return Rect()
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,16 @@
|
||||
//
|
||||
|
||||
class SceneUtils {
|
||||
static func shapeCopy(from: Shape) -> Shape {
|
||||
return Shape(form: from.form,
|
||||
fill: from.fill,
|
||||
stroke: from.stroke,
|
||||
place: from.place,
|
||||
opaque: from.opaque,
|
||||
opacity: from.opacity,
|
||||
clip: from.clip,
|
||||
effect: from.effect,
|
||||
visible: from.visible,
|
||||
tag: from.tag)
|
||||
}
|
||||
static func shapeCopy(from: Shape) -> Shape {
|
||||
return Shape(form: from.form,
|
||||
fill: from.fill,
|
||||
stroke: from.stroke,
|
||||
place: from.place,
|
||||
opaque: from.opaque,
|
||||
opacity: from.opacity,
|
||||
clip: from.clip,
|
||||
effect: from.effect,
|
||||
visible: from.visible,
|
||||
tag: from.tag)
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +1,57 @@
|
||||
import Foundation
|
||||
|
||||
open class Shape: Node {
|
||||
|
||||
open let formVar: AnimatableVariable<Locus>
|
||||
open var form: Locus {
|
||||
get { return formVar.value }
|
||||
set(val) { formVar.value = val }
|
||||
}
|
||||
|
||||
open let fillVar: AnimatableVariable<Fill?>
|
||||
open var fill: Fill? {
|
||||
get { return fillVar.value }
|
||||
set(val) { fillVar.value = val }
|
||||
}
|
||||
|
||||
open let strokeVar: AnimatableVariable<Stroke?>
|
||||
open var stroke: Stroke? {
|
||||
get { return strokeVar.value }
|
||||
set(val) { strokeVar.value = val }
|
||||
}
|
||||
|
||||
public init(form: Locus, fill: Fill? = nil, stroke: Stroke? = nil, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.formVar = AnimatableVariable<Locus>(form)
|
||||
self.fillVar = AnimatableVariable<Fill?>(fill)
|
||||
self.strokeVar = AnimatableVariable<Stroke?>(stroke)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
|
||||
open let formVar: AnimatableVariable<Locus>
|
||||
open var form: Locus {
|
||||
get { return formVar.value }
|
||||
set(val) { formVar.value = val }
|
||||
}
|
||||
|
||||
open let fillVar: AnimatableVariable<Fill?>
|
||||
open var fill: Fill? {
|
||||
get { return fillVar.value }
|
||||
set(val) { fillVar.value = val }
|
||||
}
|
||||
|
||||
open let strokeVar: AnimatableVariable<Stroke?>
|
||||
open var stroke: Stroke? {
|
||||
get { return strokeVar.value }
|
||||
set(val) { strokeVar.value = val }
|
||||
}
|
||||
|
||||
public init(form: Locus, fill: Fill? = nil, stroke: Stroke? = nil, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.formVar = AnimatableVariable<Locus>(form)
|
||||
self.fillVar = AnimatableVariable<Fill?>(fill)
|
||||
self.strokeVar = AnimatableVariable<Stroke?>(stroke)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
|
||||
self.formVar.node = self
|
||||
self.strokeVar.node = self
|
||||
self.fillVar.node = self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
override internal func bounds() -> Rect? {
|
||||
var bounds = form.bounds()
|
||||
|
||||
if let shapeStroke = self.stroke {
|
||||
let r = shapeStroke.width / 2.0
|
||||
bounds = Rect(
|
||||
x: bounds.x - r,
|
||||
y: bounds.y - r,
|
||||
w: bounds.w + r * 2.0,
|
||||
h: bounds.h + r * 2.0)
|
||||
}
|
||||
|
||||
return bounds
|
||||
self.formVar.node = self
|
||||
self.strokeVar.node = self
|
||||
self.fillVar.node = self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
override internal func bounds() -> Rect? {
|
||||
var bounds = form.bounds()
|
||||
|
||||
if let shapeStroke = self.stroke {
|
||||
let r = shapeStroke.width / 2.0
|
||||
bounds = Rect(
|
||||
x: bounds.x - r,
|
||||
y: bounds.y - r,
|
||||
w: bounds.w + r * 2.0,
|
||||
h: bounds.h + r * 2.0)
|
||||
}
|
||||
|
||||
return bounds
|
||||
}
|
||||
}
|
||||
|
@ -1,110 +1,112 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class Text: Node {
|
||||
|
||||
open let textVar: Variable<String>
|
||||
open var text: String {
|
||||
get { return textVar.value }
|
||||
set(val) { textVar.value = val }
|
||||
|
||||
open let textVar: Variable<String>
|
||||
open var text: String {
|
||||
get { return textVar.value }
|
||||
set(val) { textVar.value = val }
|
||||
}
|
||||
|
||||
open let fontVar: Variable<Font?>
|
||||
open var font: Font? {
|
||||
get { return fontVar.value }
|
||||
set(val) { fontVar.value = val }
|
||||
}
|
||||
|
||||
open let fillVar: Variable<Fill>
|
||||
open var fill: Fill {
|
||||
get { return fillVar.value }
|
||||
set(val) { fillVar.value = val }
|
||||
}
|
||||
|
||||
open let alignVar: Variable<Align>
|
||||
open var align: Align {
|
||||
get { return alignVar.value }
|
||||
set(val) { alignVar.value = val }
|
||||
}
|
||||
|
||||
open let baselineVar: Variable<Baseline>
|
||||
open var baseline: Baseline {
|
||||
get { return baselineVar.value }
|
||||
set(val) { baselineVar.value = val }
|
||||
}
|
||||
|
||||
public init(text: String, font: Font? = nil, fill: Fill = Color.black, align: Align = .min, baseline: Baseline = .top, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.textVar = Variable<String>(text)
|
||||
self.fontVar = Variable<Font?>(font)
|
||||
self.fillVar = Variable<Fill>(fill)
|
||||
self.alignVar = Variable<Align>(align)
|
||||
self.baselineVar = Variable<Baseline>(baseline)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
override internal func bounds() -> Rect {
|
||||
let font: MFont
|
||||
if let f = self.font {
|
||||
|
||||
if let customFont = RenderUtils.loadFont(name: f.name, size:f.size) {
|
||||
font = customFont
|
||||
} else {
|
||||
font = MFont.systemFont(ofSize: CGFloat(f.size))
|
||||
}
|
||||
} else {
|
||||
font = MFont.systemFont(ofSize: MFont.systemFontSize)
|
||||
}
|
||||
|
||||
open let fontVar: Variable<Font?>
|
||||
open var font: Font? {
|
||||
get { return fontVar.value }
|
||||
set(val) { fontVar.value = val }
|
||||
var stringAttributes: [String: AnyObject] = [:]
|
||||
stringAttributes[NSFontAttributeName] = font
|
||||
let size = (text as NSString).size(attributes: stringAttributes)
|
||||
return Rect(
|
||||
x: calculateAlignmentOffset(font: font),
|
||||
y: calculateBaselineOffset(font: font),
|
||||
w: size.width.doubleValue,
|
||||
h: size.height.doubleValue
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func calculateBaselineOffset(font: MFont) -> Double {
|
||||
var baselineOffset = 0.0
|
||||
switch baseline {
|
||||
case .alphabetic:
|
||||
baselineOffset = font.ascender.doubleValue
|
||||
case .bottom:
|
||||
baselineOffset = (font.ascender - font.descender).doubleValue
|
||||
case .mid:
|
||||
baselineOffset = ((font.ascender - font.descender) / 2).doubleValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
open let fillVar: Variable<Fill>
|
||||
open var fill: Fill {
|
||||
get { return fillVar.value }
|
||||
set(val) { fillVar.value = val }
|
||||
return -baselineOffset
|
||||
}
|
||||
|
||||
fileprivate func calculateAlignmentOffset(font: MFont) -> Double {
|
||||
let textAttributes = [
|
||||
NSFontAttributeName: font
|
||||
]
|
||||
let textSize = NSString(string: text).size(attributes: textAttributes)
|
||||
var alignmentOffset = 0.0
|
||||
switch align {
|
||||
case .mid:
|
||||
alignmentOffset = (textSize.width / 2).doubleValue
|
||||
case .max:
|
||||
alignmentOffset = textSize.width.doubleValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
open let alignVar: Variable<Align>
|
||||
open var align: Align {
|
||||
get { return alignVar.value }
|
||||
set(val) { alignVar.value = val }
|
||||
}
|
||||
|
||||
open let baselineVar: Variable<Baseline>
|
||||
open var baseline: Baseline {
|
||||
get { return baselineVar.value }
|
||||
set(val) { baselineVar.value = val }
|
||||
}
|
||||
|
||||
public init(text: String, font: Font? = nil, fill: Fill = Color.black, align: Align = .min, baseline: Baseline = .top, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
|
||||
self.textVar = Variable<String>(text)
|
||||
self.fontVar = Variable<Font?>(font)
|
||||
self.fillVar = Variable<Fill>(fill)
|
||||
self.alignVar = Variable<Align>(align)
|
||||
self.baselineVar = Variable<Baseline>(baseline)
|
||||
super.init(
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
override internal func bounds() -> Rect {
|
||||
let font: UIFont
|
||||
if let f = self.font {
|
||||
|
||||
if let customFont = RenderUtils.loadFont(name: f.name, size:f.size) {
|
||||
font = customFont
|
||||
} else {
|
||||
font = UIFont.systemFont(ofSize: CGFloat(f.size))
|
||||
}
|
||||
} else {
|
||||
font = UIFont.systemFont(ofSize: UIFont.systemFontSize)
|
||||
}
|
||||
var stringAttributes: [String: AnyObject] = [:]
|
||||
stringAttributes[NSFontAttributeName] = font
|
||||
let size = (text as NSString).size(attributes: stringAttributes)
|
||||
return Rect(
|
||||
x: calculateAlignmentOffset(font: font),
|
||||
y: calculateBaselineOffset(font: font),
|
||||
w: size.width.doubleValue,
|
||||
h: size.height.doubleValue
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func calculateBaselineOffset(font: UIFont) -> Double {
|
||||
var baselineOffset = 0.0
|
||||
switch baseline {
|
||||
case .alphabetic:
|
||||
baselineOffset = font.ascender.doubleValue
|
||||
case .bottom:
|
||||
baselineOffset = (font.ascender - font.descender).doubleValue
|
||||
case .mid:
|
||||
baselineOffset = ((font.ascender - font.descender) / 2).doubleValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
return -baselineOffset
|
||||
}
|
||||
|
||||
fileprivate func calculateAlignmentOffset(font: UIFont) -> Double {
|
||||
let textAttributes = [
|
||||
NSFontAttributeName: font
|
||||
]
|
||||
let textSize = NSString(string: text).size(attributes: textAttributes)
|
||||
var alignmentOffset = 0.0
|
||||
switch align {
|
||||
case .mid:
|
||||
alignmentOffset = (textSize.width / 2).doubleValue
|
||||
case .max:
|
||||
alignmentOffset = textSize.width.doubleValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
return -alignmentOffset
|
||||
}
|
||||
|
||||
return -alignmentOffset
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,78 +1,79 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
class GroupRenderer: NodeRenderer {
|
||||
|
||||
weak var group: Group?
|
||||
|
||||
fileprivate var renderers: [NodeRenderer] = []
|
||||
let renderingInterval: RenderingInterval?
|
||||
|
||||
init(group: Group, ctx: RenderContext, animationCache: AnimationCache?, interval: RenderingInterval? = .none) {
|
||||
self.group = group
|
||||
self.renderingInterval = interval
|
||||
super.init(node: group, ctx: ctx, animationCache: animationCache)
|
||||
updateRenderers()
|
||||
}
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
guard let group = group else {
|
||||
return
|
||||
}
|
||||
|
||||
group.contentsVar.onChange { [weak self] _ in
|
||||
self?.updateRenderers()
|
||||
}
|
||||
observe(group.contentsVar)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
return group
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
renderers.forEach { renderer in
|
||||
renderer.render(force: force, opacity: opacity)
|
||||
}
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
for renderer in renderers.reversed() {
|
||||
if let node = renderer.findNodeAt(location: location, ctx: ctx) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
weak var group: Group?
|
||||
|
||||
fileprivate var renderers: [NodeRenderer] = []
|
||||
let renderingInterval: RenderingInterval?
|
||||
|
||||
init(group: Group, ctx: RenderContext, animationCache: AnimationCache?, interval: RenderingInterval? = .none) {
|
||||
self.group = group
|
||||
self.renderingInterval = interval
|
||||
super.init(node: group, ctx: ctx, animationCache: animationCache)
|
||||
updateRenderers()
|
||||
}
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
guard let group = group else {
|
||||
return
|
||||
}
|
||||
|
||||
override func dispose() {
|
||||
super.dispose()
|
||||
renderers.forEach { renderer in renderer.dispose() }
|
||||
renderers = []
|
||||
}
|
||||
|
||||
private func updateRenderers() {
|
||||
renderers.forEach{ $0.dispose() }
|
||||
renderers.removeAll()
|
||||
|
||||
if let updatedRenderers = group?.contents.flatMap ({ (child) -> NodeRenderer? in
|
||||
guard let interval = renderingInterval else {
|
||||
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache)
|
||||
}
|
||||
|
||||
let index = AnimationUtils.absoluteIndex(child, useCache: true)
|
||||
if index > interval.from && index < interval.to {
|
||||
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache, interval: interval)
|
||||
}
|
||||
|
||||
return .none
|
||||
|
||||
}) {
|
||||
renderers = updatedRenderers
|
||||
}
|
||||
}
|
||||
|
||||
group.contentsVar.onChange { [weak self] _ in
|
||||
self?.updateRenderers()
|
||||
}
|
||||
observe(group.contentsVar)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
return group
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
renderers.forEach { renderer in
|
||||
renderer.render(force: force, opacity: opacity)
|
||||
}
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
for renderer in renderers.reversed() {
|
||||
if let node = renderer.findNodeAt(location: location, ctx: ctx) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func dispose() {
|
||||
super.dispose()
|
||||
renderers.forEach { renderer in renderer.dispose() }
|
||||
renderers = []
|
||||
}
|
||||
|
||||
private func updateRenderers() {
|
||||
renderers.forEach{ $0.dispose() }
|
||||
renderers.removeAll()
|
||||
|
||||
if let updatedRenderers = group?.contents.flatMap ({ (child) -> NodeRenderer? in
|
||||
guard let interval = renderingInterval else {
|
||||
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache)
|
||||
}
|
||||
|
||||
let index = AnimationUtils.absoluteIndex(child, useCache: true)
|
||||
if index > interval.from && index < interval.to {
|
||||
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache, interval: interval)
|
||||
}
|
||||
|
||||
return .none
|
||||
|
||||
}) {
|
||||
renderers = updatedRenderers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,174 +1,175 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
class ImageRenderer: NodeRenderer {
|
||||
weak var image: Image?
|
||||
|
||||
var renderedPaths: [CGPath] = [CGPath]()
|
||||
|
||||
init(image: Image, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.image = image
|
||||
super.init(node: image, ctx: ctx, animationCache: animationCache)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
return image
|
||||
}
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
observe(image.srcVar)
|
||||
observe(image.xAlignVar)
|
||||
observe(image.yAlignVar)
|
||||
observe(image.aspectRatioVar)
|
||||
observe(image.wVar)
|
||||
observe(image.hVar)
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
var uiimage: UIImage?
|
||||
if image.src.contains("memory") {
|
||||
let id = image.src.replacingOccurrences(of: "memory://", with: "")
|
||||
uiimage = imagesMap[id]
|
||||
} else {
|
||||
uiimage = image.image()
|
||||
}
|
||||
|
||||
if let uiimage = uiimage {
|
||||
let rect = getRect(uiimage)
|
||||
ctx.cgContext!.scaleBy(x: 1.0, y: -1.0)
|
||||
ctx.cgContext!.translateBy(x: 0.0, y: -1.0 * rect.height)
|
||||
ctx.cgContext!.setAlpha(CGFloat(opacity))
|
||||
ctx.cgContext!.draw(uiimage.cgImage!, in: rect)
|
||||
}
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let image = image else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let uiimage = UIImage(named: image.src) {
|
||||
let rect = getRect(uiimage)
|
||||
if (rect.contains(location)) {
|
||||
return node()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
weak var image: Image?
|
||||
|
||||
var renderedPaths: [CGPath] = [CGPath]()
|
||||
|
||||
init(image: Image, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.image = image
|
||||
super.init(node: image, ctx: ctx, animationCache: animationCache)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
return image
|
||||
}
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
fileprivate func getRect(_ uiimage: UIImage) -> CGRect {
|
||||
guard let image = image else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
let imageSize = uiimage.size
|
||||
var w = CGFloat(image.w)
|
||||
var h = CGFloat(image.h)
|
||||
if ((w == 0 || w == imageSize.width) && (h == 0 || h == imageSize.height)) {
|
||||
return CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
|
||||
} else {
|
||||
if (w == 0) {
|
||||
w = imageSize.width * h / imageSize.height
|
||||
} else if (h == 0) {
|
||||
h = imageSize.height * w / imageSize.width
|
||||
}
|
||||
switch (image.aspectRatio) {
|
||||
case AspectRatio.meet:
|
||||
return calculateMeetAspectRatio(image, size: imageSize)
|
||||
case AspectRatio.slice:
|
||||
return calculateSliceAspectRatio(image, size: imageSize)
|
||||
//ctx.cgContext!.clip(to: CGRect(x: 0, y: 0, width: w, height: h))
|
||||
default:
|
||||
return CGRect(x: 0, y: 0, width: w, height: h)
|
||||
}
|
||||
}
|
||||
|
||||
observe(image.srcVar)
|
||||
observe(image.xAlignVar)
|
||||
observe(image.yAlignVar)
|
||||
observe(image.aspectRatioVar)
|
||||
observe(image.wVar)
|
||||
observe(image.hVar)
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
fileprivate func calculateMeetAspectRatio(_ image: Image, size: CGSize) -> CGRect {
|
||||
let w = CGFloat(image.w)
|
||||
let h = CGFloat(image.h)
|
||||
// destination and source aspect ratios
|
||||
let destAR = w / h
|
||||
let srcAR = size.width / size.height
|
||||
var resultW = w
|
||||
var resultH = h
|
||||
var destX = CGFloat(0)
|
||||
var destY = CGFloat(0)
|
||||
if (destAR < srcAR) {
|
||||
// fill all available width and scale height
|
||||
resultH = size.height * w / size.width
|
||||
} else {
|
||||
// fill all available height and scale width
|
||||
resultW = size.width * h / size.height
|
||||
}
|
||||
let xalign = image.xAlign
|
||||
switch (xalign) {
|
||||
case Align.min:
|
||||
destX = 0
|
||||
case Align.mid:
|
||||
destX = w / 2 - resultW / 2
|
||||
case Align.max:
|
||||
destX = w - resultW
|
||||
}
|
||||
let yalign = image.yAlign
|
||||
switch (yalign) {
|
||||
case Align.min:
|
||||
destY = 0
|
||||
case Align.mid:
|
||||
destY = h / 2 - resultH / 2
|
||||
case Align.max:
|
||||
destY = h - resultH
|
||||
}
|
||||
return CGRect(x: destX, y: destY, width: resultW, height: resultH)
|
||||
}
|
||||
|
||||
fileprivate func calculateSliceAspectRatio(_ image: Image, size: CGSize) -> CGRect {
|
||||
let w = CGFloat(image.w)
|
||||
let h = CGFloat(image.h)
|
||||
var srcX = CGFloat(0)
|
||||
var srcY = CGFloat(0)
|
||||
var totalH: CGFloat = 0
|
||||
var totalW: CGFloat = 0
|
||||
// destination and source aspect ratios
|
||||
let destAR = w / h
|
||||
let srcAR = size.width / size.height
|
||||
if (destAR > srcAR) {
|
||||
// fill all available width and scale height
|
||||
totalH = size.height * w / size.width
|
||||
totalW = w
|
||||
switch (image.yAlign) {
|
||||
case Align.min:
|
||||
srcY = 0
|
||||
case Align.mid:
|
||||
srcY = -(totalH / 2 - h / 2)
|
||||
case Align.max:
|
||||
srcY = -(totalH - h)
|
||||
}
|
||||
} else {
|
||||
// fill all available height and scale width
|
||||
totalW = size.width * h / size.height
|
||||
totalH = h
|
||||
switch (image.xAlign) {
|
||||
case Align.min:
|
||||
srcX = 0
|
||||
case Align.mid:
|
||||
srcX = -(totalW / 2 - w / 2)
|
||||
case Align.max:
|
||||
srcX = -(totalW - w)
|
||||
}
|
||||
}
|
||||
return CGRect(x: srcX, y: srcY, width: totalW, height: totalH)
|
||||
}
|
||||
|
||||
var mImage: MImage?
|
||||
if image.src.contains("memory") {
|
||||
let id = image.src.replacingOccurrences(of: "memory://", with: "")
|
||||
mImage = imagesMap[id]
|
||||
} else {
|
||||
mImage = image.image()
|
||||
}
|
||||
|
||||
if let mImage = mImage {
|
||||
let rect = getRect(mImage)
|
||||
ctx.cgContext!.scaleBy(x: 1.0, y: -1.0)
|
||||
ctx.cgContext!.translateBy(x: 0.0, y: -1.0 * rect.height)
|
||||
ctx.cgContext!.setAlpha(CGFloat(opacity))
|
||||
ctx.cgContext!.draw(mImage.cgImage!, in: rect)
|
||||
}
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let image = image else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let mImage = MImage(named: image.src) {
|
||||
let rect = getRect(mImage)
|
||||
if (rect.contains(location)) {
|
||||
return node()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func getRect(_ mImage: MImage) -> CGRect {
|
||||
guard let image = image else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
let imageSize = mImage.size
|
||||
var w = CGFloat(image.w)
|
||||
var h = CGFloat(image.h)
|
||||
if ((w == 0 || w == imageSize.width) && (h == 0 || h == imageSize.height)) {
|
||||
return CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
|
||||
} else {
|
||||
if (w == 0) {
|
||||
w = imageSize.width * h / imageSize.height
|
||||
} else if (h == 0) {
|
||||
h = imageSize.height * w / imageSize.width
|
||||
}
|
||||
switch (image.aspectRatio) {
|
||||
case AspectRatio.meet:
|
||||
return calculateMeetAspectRatio(image, size: imageSize)
|
||||
case AspectRatio.slice:
|
||||
return calculateSliceAspectRatio(image, size: imageSize)
|
||||
//ctx.cgContext!.clip(to: CGRect(x: 0, y: 0, width: w, height: h))
|
||||
default:
|
||||
return CGRect(x: 0, y: 0, width: w, height: h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func calculateMeetAspectRatio(_ image: Image, size: CGSize) -> CGRect {
|
||||
let w = CGFloat(image.w)
|
||||
let h = CGFloat(image.h)
|
||||
// destination and source aspect ratios
|
||||
let destAR = w / h
|
||||
let srcAR = size.width / size.height
|
||||
var resultW = w
|
||||
var resultH = h
|
||||
var destX = CGFloat(0)
|
||||
var destY = CGFloat(0)
|
||||
if (destAR < srcAR) {
|
||||
// fill all available width and scale height
|
||||
resultH = size.height * w / size.width
|
||||
} else {
|
||||
// fill all available height and scale width
|
||||
resultW = size.width * h / size.height
|
||||
}
|
||||
let xalign = image.xAlign
|
||||
switch (xalign) {
|
||||
case Align.min:
|
||||
destX = 0
|
||||
case Align.mid:
|
||||
destX = w / 2 - resultW / 2
|
||||
case Align.max:
|
||||
destX = w - resultW
|
||||
}
|
||||
let yalign = image.yAlign
|
||||
switch (yalign) {
|
||||
case Align.min:
|
||||
destY = 0
|
||||
case Align.mid:
|
||||
destY = h / 2 - resultH / 2
|
||||
case Align.max:
|
||||
destY = h - resultH
|
||||
}
|
||||
return CGRect(x: destX, y: destY, width: resultW, height: resultH)
|
||||
}
|
||||
|
||||
fileprivate func calculateSliceAspectRatio(_ image: Image, size: CGSize) -> CGRect {
|
||||
let w = CGFloat(image.w)
|
||||
let h = CGFloat(image.h)
|
||||
var srcX = CGFloat(0)
|
||||
var srcY = CGFloat(0)
|
||||
var totalH: CGFloat = 0
|
||||
var totalW: CGFloat = 0
|
||||
// destination and source aspect ratios
|
||||
let destAR = w / h
|
||||
let srcAR = size.width / size.height
|
||||
if (destAR > srcAR) {
|
||||
// fill all available width and scale height
|
||||
totalH = size.height * w / size.width
|
||||
totalW = w
|
||||
switch (image.yAlign) {
|
||||
case Align.min:
|
||||
srcY = 0
|
||||
case Align.mid:
|
||||
srcY = -(totalH / 2 - h / 2)
|
||||
case Align.max:
|
||||
srcY = -(totalH - h)
|
||||
}
|
||||
} else {
|
||||
// fill all available height and scale width
|
||||
totalW = size.width * h / size.height
|
||||
totalH = h
|
||||
switch (image.xAlign) {
|
||||
case Align.min:
|
||||
srcX = 0
|
||||
case Align.mid:
|
||||
srcX = -(totalW / 2 - w / 2)
|
||||
case Align.max:
|
||||
srcX = -(totalW - w)
|
||||
}
|
||||
}
|
||||
return CGRect(x: srcX, y: srcY, width: totalW, height: totalH)
|
||||
}
|
||||
}
|
||||
|
@ -1,156 +1,157 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct RenderingInterval {
|
||||
let from: Int
|
||||
let to: Int
|
||||
let from: Int
|
||||
let to: Int
|
||||
}
|
||||
|
||||
class NodeRenderer {
|
||||
|
||||
let ctx: RenderContext
|
||||
|
||||
fileprivate let onNodeChange: ()->()
|
||||
fileprivate let disposables = GroupDisposable()
|
||||
fileprivate var active = false
|
||||
weak var animationCache: AnimationCache?
|
||||
|
||||
init(node: Node, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.ctx = ctx
|
||||
self.animationCache = animationCache
|
||||
onNodeChange = { ctx.view?.setNeedsDisplay() }
|
||||
addObservers()
|
||||
|
||||
let ctx: RenderContext
|
||||
|
||||
fileprivate let onNodeChange: ()->()
|
||||
fileprivate let disposables = GroupDisposable()
|
||||
fileprivate var active = false
|
||||
weak var animationCache: AnimationCache?
|
||||
|
||||
init(node: Node, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.ctx = ctx
|
||||
self.animationCache = animationCache
|
||||
onNodeChange = { ctx.view?.setNeedsDisplay() }
|
||||
addObservers()
|
||||
}
|
||||
|
||||
func doAddObservers() {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
func doAddObservers() {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
observe(node.placeVar)
|
||||
observe(node.opaqueVar)
|
||||
observe(node.opacityVar)
|
||||
observe(node.clipVar)
|
||||
observe(node.effectVar)
|
||||
observe(node.placeVar)
|
||||
observe(node.opaqueVar)
|
||||
observe(node.opacityVar)
|
||||
observe(node.clipVar)
|
||||
observe(node.effectVar)
|
||||
}
|
||||
|
||||
func observe<E>(_ v: Variable<E>) {
|
||||
let disposable = v.onChange { [weak self] _ in
|
||||
self?.onNodeChange()
|
||||
}
|
||||
|
||||
func observe<E>(_ v: Variable<E>) {
|
||||
let disposable = v.onChange { [weak self] _ in
|
||||
self?.onNodeChange()
|
||||
}
|
||||
|
||||
addDisposable(disposable)
|
||||
addDisposable(disposable)
|
||||
}
|
||||
|
||||
func addDisposable(_ disposable: Disposable) {
|
||||
disposable.addTo(disposables)
|
||||
}
|
||||
|
||||
open func dispose() {
|
||||
removeObservers()
|
||||
}
|
||||
|
||||
open func node() -> Node? {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
final public func render(force: Bool, opacity: Double) {
|
||||
ctx.cgContext!.saveGState()
|
||||
defer {
|
||||
ctx.cgContext!.restoreGState()
|
||||
}
|
||||
|
||||
func addDisposable(_ disposable: Disposable) {
|
||||
disposable.addTo(disposables)
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
open func dispose() {
|
||||
removeObservers()
|
||||
ctx.cgContext!.concatenate(RenderUtils.mapTransform(node.place))
|
||||
applyClip()
|
||||
directRender(force: force, opacity: node.opacity * opacity)
|
||||
}
|
||||
|
||||
final func directRender(force: Bool = true, opacity: Double = 1.0) {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
open func node() -> Node? {
|
||||
fatalError("Unsupported")
|
||||
if let isAnimating = animationCache?.isAnimating(node), isAnimating {
|
||||
self.removeObservers()
|
||||
if (!force) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
self.addObservers()
|
||||
}
|
||||
doRender(force, opacity: opacity)
|
||||
}
|
||||
|
||||
func doRender(_ force: Bool, opacity: Double) {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
public final func findNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let node = node() else {
|
||||
return .none
|
||||
}
|
||||
|
||||
final public func render(force: Bool, opacity: Double) {
|
||||
ctx.cgContext!.saveGState()
|
||||
if (node.opaque) {
|
||||
let place = node.place
|
||||
if let inverted = place.invert() {
|
||||
ctx.saveGState()
|
||||
defer {
|
||||
ctx.cgContext!.restoreGState()
|
||||
ctx.restoreGState()
|
||||
}
|
||||
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.cgContext!.concatenate(RenderUtils.mapTransform(node.place))
|
||||
ctx.concatenate(RenderUtils.mapTransform(place))
|
||||
applyClip()
|
||||
directRender(force: force, opacity: node.opacity * opacity)
|
||||
let loc = location.applying(RenderUtils.mapTransform(inverted))
|
||||
let result = doFindNodeAt(location: CGPoint(x: loc.x, y: loc.y), ctx: ctx)
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func applyClip() {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
final func directRender(force: Bool = true, opacity: Double = 1.0) {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
if let isAnimating = animationCache?.isAnimating(node), isAnimating {
|
||||
self.removeObservers()
|
||||
if (!force) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
self.addObservers()
|
||||
}
|
||||
doRender(force, opacity: opacity)
|
||||
guard let clip = node.clip, let context = ctx.cgContext else {
|
||||
return
|
||||
}
|
||||
|
||||
func doRender(_ force: Bool, opacity: Double) {
|
||||
fatalError("Unsupported")
|
||||
UIGraphicsPushContext(context)
|
||||
defer {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
public final func findNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let node = node() else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if (node.opaque) {
|
||||
let place = node.place
|
||||
if let inverted = place.invert() {
|
||||
ctx.saveGState()
|
||||
defer {
|
||||
ctx.restoreGState()
|
||||
}
|
||||
|
||||
ctx.concatenate(RenderUtils.mapTransform(place))
|
||||
applyClip()
|
||||
let loc = location.applying(RenderUtils.mapTransform(inverted))
|
||||
let result = doFindNodeAt(location: CGPoint(x: loc.x, y: loc.y), ctx: ctx)
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if let rect = clip as? Rect {
|
||||
context.clip(to: CGRect(x: rect.x, y: rect.y, width: rect.w, height: rect.h))
|
||||
return
|
||||
}
|
||||
|
||||
public func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
return nil
|
||||
RenderUtils.toBezierPath(clip).addClip()
|
||||
}
|
||||
|
||||
private func addObservers() {
|
||||
if (!active) {
|
||||
active = true
|
||||
doAddObservers()
|
||||
}
|
||||
|
||||
private func applyClip() {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let clip = node.clip, let context = ctx.cgContext else {
|
||||
return
|
||||
}
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
defer {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
if let rect = clip as? Rect {
|
||||
context.clip(to: CGRect(x: rect.x, y: rect.y, width: rect.w, height: rect.h))
|
||||
return
|
||||
}
|
||||
|
||||
RenderUtils.toBezierPath(clip).addClip()
|
||||
}
|
||||
|
||||
private func addObservers() {
|
||||
if (!active) {
|
||||
active = true
|
||||
doAddObservers()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func removeObservers() {
|
||||
if (active) {
|
||||
active = false
|
||||
disposables.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func removeObservers() {
|
||||
if (active) {
|
||||
active = false
|
||||
disposables.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
class RenderContext {
|
||||
weak var view: UIView?
|
||||
weak var cgContext: CGContext?
|
||||
|
||||
init(view: UIView?) {
|
||||
self.view = view
|
||||
self.cgContext = nil
|
||||
}
|
||||
weak var view: MView?
|
||||
weak var cgContext: CGContext?
|
||||
|
||||
init(view: MView?) {
|
||||
self.view = view
|
||||
self.cgContext = nil
|
||||
}
|
||||
}
|
||||
|
@ -1,461 +1,462 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
class RenderUtils {
|
||||
class func mapColor(_ color: Color) -> CGColor {
|
||||
let red = CGFloat(Double(color.r()) / 255.0)
|
||||
let green = CGFloat(Double(color.g()) / 255.0)
|
||||
let blue = CGFloat(Double(color.b()) / 255.0)
|
||||
let alpha = CGFloat(Double(color.a()) / 255.0)
|
||||
return UIColor(red: red, green: green, blue: blue, alpha: alpha).cgColor
|
||||
class func mapColor(_ color: Color) -> CGColor {
|
||||
let red = CGFloat(Double(color.r()) / 255.0)
|
||||
let green = CGFloat(Double(color.g()) / 255.0)
|
||||
let blue = CGFloat(Double(color.b()) / 255.0)
|
||||
let alpha = CGFloat(Double(color.a()) / 255.0)
|
||||
return MColor(red: red, green: green, blue: blue, alpha: alpha).cgColor
|
||||
}
|
||||
|
||||
class func mapTransform(_ t: Transform) -> CGAffineTransform {
|
||||
return CGAffineTransform(a: CGFloat(t.m11), b: CGFloat(t.m12), c: CGFloat(t.m21),
|
||||
d: CGFloat(t.m22), tx: CGFloat(t.dx), ty: CGFloat(t.dy))
|
||||
}
|
||||
|
||||
class func mapLineJoin(_ join: LineJoin?) -> CGLineJoin {
|
||||
switch join {
|
||||
case LineJoin.round?: return CGLineJoin.round
|
||||
case LineJoin.bevel?: return CGLineJoin.bevel
|
||||
default: return CGLineJoin.miter
|
||||
}
|
||||
}
|
||||
|
||||
class func mapLineJoinToString(_ join: LineJoin?) -> String {
|
||||
switch join {
|
||||
case LineJoin.round?: return kCALineJoinRound
|
||||
case LineJoin.bevel?: return kCALineJoinBevel
|
||||
default: return kCALineJoinMiter
|
||||
}
|
||||
}
|
||||
|
||||
class func mapLineCap(_ cap: LineCap?) -> CGLineCap {
|
||||
switch cap {
|
||||
case LineCap.round?: return CGLineCap.round
|
||||
case LineCap.square?: return CGLineCap.square
|
||||
default: return CGLineCap.butt
|
||||
}
|
||||
}
|
||||
|
||||
class func mapLineCapToString(_ cap: LineCap?) -> String {
|
||||
switch cap {
|
||||
case LineCap.round?: return kCALineCapRound
|
||||
case LineCap.square?: return kCALineCapSquare
|
||||
default: return kCALineCapButt
|
||||
}
|
||||
}
|
||||
|
||||
class func mapDash(_ dashes: [Double]) -> UnsafeMutablePointer<CGFloat> {
|
||||
let p = UnsafeMutablePointer<CGFloat>.allocate(capacity:dashes.count * MemoryLayout<CGFloat>.size)
|
||||
for (index, item) in dashes.enumerated() {
|
||||
p[index] = CGFloat(item)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
class func createNodeRenderer(
|
||||
_ node: Node,
|
||||
context: RenderContext,
|
||||
animationCache: AnimationCache?,
|
||||
interval: RenderingInterval? = .none
|
||||
) -> NodeRenderer {
|
||||
if let group = node as? Group {
|
||||
return GroupRenderer(group: group, ctx: context, animationCache: animationCache, interval: interval)
|
||||
} else if let shape = node as? Shape {
|
||||
return ShapeRenderer(shape: shape, ctx: context, animationCache: animationCache)
|
||||
} else if let text = node as? Text {
|
||||
return TextRenderer(text: text, ctx: context, animationCache: animationCache)
|
||||
} else if let image = node as? Image {
|
||||
return ImageRenderer(image: image, ctx: context, animationCache: animationCache)
|
||||
}
|
||||
fatalError("Unsupported node: \(node)");
|
||||
}
|
||||
|
||||
class func loadFont(name: String, size: Int) -> MFont? {
|
||||
let separationSet = CharacterSet(charactersIn:",")
|
||||
let names = name.components(separatedBy: separationSet)
|
||||
var customFont: MFont? = .none
|
||||
names.forEach { fontName in
|
||||
if customFont != .none {
|
||||
return
|
||||
}
|
||||
|
||||
if fontName.characters.first == " " {
|
||||
let index = fontName.index(fontName.startIndex, offsetBy:1)
|
||||
let fixedName = fontName.substring(from: index)
|
||||
customFont = MFont(name: fixedName, size: CGFloat(size))
|
||||
return
|
||||
}
|
||||
|
||||
customFont = MFont(name: fontName, size: CGFloat(size))
|
||||
}
|
||||
|
||||
class func mapTransform(_ t: Transform) -> CGAffineTransform {
|
||||
return CGAffineTransform(a: CGFloat(t.m11), b: CGFloat(t.m12), c: CGFloat(t.m21),
|
||||
d: CGFloat(t.m22), tx: CGFloat(t.dx), ty: CGFloat(t.dy))
|
||||
}
|
||||
|
||||
class func mapLineJoin(_ join: LineJoin?) -> CGLineJoin {
|
||||
switch join {
|
||||
case LineJoin.round?: return CGLineJoin.round
|
||||
case LineJoin.bevel?: return CGLineJoin.bevel
|
||||
default: return CGLineJoin.miter
|
||||
}
|
||||
}
|
||||
|
||||
class func mapLineJoinToString(_ join: LineJoin?) -> String {
|
||||
switch join {
|
||||
case LineJoin.round?: return kCALineJoinRound
|
||||
case LineJoin.bevel?: return kCALineJoinBevel
|
||||
default: return kCALineJoinMiter
|
||||
}
|
||||
}
|
||||
|
||||
class func mapLineCap(_ cap: LineCap?) -> CGLineCap {
|
||||
switch cap {
|
||||
case LineCap.round?: return CGLineCap.round
|
||||
case LineCap.square?: return CGLineCap.square
|
||||
default: return CGLineCap.butt
|
||||
}
|
||||
}
|
||||
|
||||
class func mapLineCapToString(_ cap: LineCap?) -> String {
|
||||
switch cap {
|
||||
case LineCap.round?: return kCALineCapRound
|
||||
case LineCap.square?: return kCALineCapSquare
|
||||
default: return kCALineCapButt
|
||||
}
|
||||
}
|
||||
|
||||
class func mapDash(_ dashes: [Double]) -> UnsafeMutablePointer<CGFloat> {
|
||||
let p = UnsafeMutablePointer<CGFloat>.allocate(capacity:dashes.count * MemoryLayout<CGFloat>.size)
|
||||
for (index, item) in dashes.enumerated() {
|
||||
p[index] = CGFloat(item)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
class func createNodeRenderer(
|
||||
_ node: Node,
|
||||
context: RenderContext,
|
||||
animationCache: AnimationCache?,
|
||||
interval: RenderingInterval? = .none
|
||||
) -> NodeRenderer {
|
||||
if let group = node as? Group {
|
||||
return GroupRenderer(group: group, ctx: context, animationCache: animationCache, interval: interval)
|
||||
} else if let shape = node as? Shape {
|
||||
return ShapeRenderer(shape: shape, ctx: context, animationCache: animationCache)
|
||||
} else if let text = node as? Text {
|
||||
return TextRenderer(text: text, ctx: context, animationCache: animationCache)
|
||||
} else if let image = node as? Image {
|
||||
return ImageRenderer(image: image, ctx: context, animationCache: animationCache)
|
||||
}
|
||||
fatalError("Unsupported node: \(node)");
|
||||
}
|
||||
|
||||
class func loadFont(name: String, size: Int) -> UIFont? {
|
||||
let separationSet = CharacterSet(charactersIn:",")
|
||||
let names = name.components(separatedBy: separationSet)
|
||||
var customFont: UIFont? = .none
|
||||
names.forEach { fontName in
|
||||
if customFont != .none {
|
||||
return
|
||||
}
|
||||
|
||||
if fontName.characters.first == " " {
|
||||
let index = fontName.index(fontName.startIndex, offsetBy:1)
|
||||
let fixedName = fontName.substring(from: index)
|
||||
customFont = UIFont(name: fixedName, size: CGFloat(size))
|
||||
return
|
||||
}
|
||||
|
||||
customFont = UIFont(name: fontName, size: CGFloat(size))
|
||||
}
|
||||
|
||||
return customFont
|
||||
}
|
||||
|
||||
class func applyOpacity(_ color: Color, opacity: Double) -> Color {
|
||||
return Color.rgba(r: color.r(), g: color.g(), b: color.b(), a: Double(color.a()) / 255.0 * opacity)
|
||||
}
|
||||
|
||||
class func toCGPath(_ locus: Locus) -> CGPath {
|
||||
if let arc = locus as? Arc {
|
||||
if arc.ellipse.rx != arc.ellipse.ry {
|
||||
// http://stackoverflow.com/questions/11365775/how-to-draw-an-elliptical-arc-with-coregraphics
|
||||
// input parameters
|
||||
let ellipse = arc.ellipse
|
||||
let startAngle = CGFloat(arc.shift)
|
||||
let endAngle = startAngle + CGFloat(arc.extent)
|
||||
let r = CGFloat(ellipse.rx)
|
||||
let scale = CGFloat(ellipse.ry / ellipse.rx)
|
||||
let path = CGMutablePath()
|
||||
var t = CGAffineTransform(translationX: CGFloat(ellipse.cx), y: CGFloat(ellipse.cy))
|
||||
t = CGAffineTransform(scaleX: 1.0, y: scale).concatenating(t)
|
||||
path.addArc(center: CGPoint.zero, radius: r, startAngle: startAngle, endAngle: endAngle, clockwise: false, transform: t)
|
||||
return path
|
||||
}
|
||||
}
|
||||
return toBezierPath(locus).reversing().cgPath
|
||||
}
|
||||
|
||||
class func toBezierPath(_ locus: Locus) -> UIBezierPath {
|
||||
if let round = locus as? RoundRect {
|
||||
let corners = CGSize(width: CGFloat(round.rx), height: CGFloat(round.ry))
|
||||
return UIBezierPath(roundedRect: newCGRect(round.rect), byRoundingCorners:
|
||||
UIRectCorner.allCorners, cornerRadii: corners)
|
||||
} else if let arc = locus as? Arc {
|
||||
if arc.ellipse.rx == arc.ellipse.ry {
|
||||
return arcToPath(arc)
|
||||
}
|
||||
} else if let point = locus as? Point {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: CGFloat(point.x), y: CGFloat(point.y)))
|
||||
path.addLine(to: CGPoint(x: CGFloat(point.x), y: CGFloat(point.y)))
|
||||
return path
|
||||
} else if let line = locus as? Line {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: CGFloat(line.x1), y: CGFloat(line.y1)))
|
||||
path.addLine(to: CGPoint(x: CGFloat(line.x2), y: CGFloat(line.y2)))
|
||||
return path
|
||||
} else if let polygon = locus as? Polygon {
|
||||
let path = pointsToPath(polygon.points)
|
||||
path.close()
|
||||
return path
|
||||
} else if let polygon = locus as? Polyline {
|
||||
return pointsToPath(polygon.points)
|
||||
} else if let rect = locus as? Rect {
|
||||
return UIBezierPath(rect: rect.cgRect())
|
||||
} else if let circle = locus as? Circle {
|
||||
return UIBezierPath(ovalIn: circle.bounds().cgRect())
|
||||
} else if let path = locus as? Path {
|
||||
return toBezierPath(path)
|
||||
}
|
||||
fatalError("Unsupported locus: \(locus)")
|
||||
}
|
||||
|
||||
fileprivate class func arcToPath(_ arc: Arc) -> UIBezierPath {
|
||||
let shift = CGFloat(arc.shift)
|
||||
let end = shift + CGFloat(arc.extent)
|
||||
return customFont
|
||||
}
|
||||
|
||||
class func applyOpacity(_ color: Color, opacity: Double) -> Color {
|
||||
return Color.rgba(r: color.r(), g: color.g(), b: color.b(), a: Double(color.a()) / 255.0 * opacity)
|
||||
}
|
||||
|
||||
class func toCGPath(_ locus: Locus) -> CGPath {
|
||||
if let arc = locus as? Arc {
|
||||
if arc.ellipse.rx != arc.ellipse.ry {
|
||||
// http://stackoverflow.com/questions/11365775/how-to-draw-an-elliptical-arc-with-coregraphics
|
||||
// input parameters
|
||||
let ellipse = arc.ellipse
|
||||
let center = CGPoint(x: CGFloat(ellipse.cx), y: CGFloat(ellipse.cy))
|
||||
return UIBezierPath(arcCenter: center, radius: CGFloat(ellipse.rx), startAngle: shift, endAngle: end, clockwise: true)
|
||||
}
|
||||
|
||||
fileprivate class func pointsToPath(_ points: [Double]) -> UIBezierPath {
|
||||
let parts = stride(from: 0, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) }
|
||||
let path = UIBezierPath()
|
||||
var first = true
|
||||
for part in parts {
|
||||
let point = CGPoint(x: CGFloat(part[0]), y: CGFloat(part[1]))
|
||||
if (first) {
|
||||
path.move(to: point)
|
||||
first = false
|
||||
} else {
|
||||
path.addLine(to: point)
|
||||
}
|
||||
}
|
||||
let startAngle = CGFloat(arc.shift)
|
||||
let endAngle = startAngle + CGFloat(arc.extent)
|
||||
let r = CGFloat(ellipse.rx)
|
||||
let scale = CGFloat(ellipse.ry / ellipse.rx)
|
||||
let path = CGMutablePath()
|
||||
var t = CGAffineTransform(translationX: CGFloat(ellipse.cx), y: CGFloat(ellipse.cy))
|
||||
t = CGAffineTransform(scaleX: 1.0, y: scale).concatenating(t)
|
||||
path.addArc(center: CGPoint.zero, radius: r, startAngle: startAngle, endAngle: endAngle, clockwise: false, transform: t)
|
||||
return path
|
||||
}
|
||||
}
|
||||
return toBezierPath(locus).reversing().cgPath
|
||||
}
|
||||
|
||||
class func toBezierPath(_ locus: Locus) -> UIBezierPath {
|
||||
if let round = locus as? RoundRect {
|
||||
let corners = CGSize(width: CGFloat(round.rx), height: CGFloat(round.ry))
|
||||
return UIBezierPath(roundedRect: newCGRect(round.rect), byRoundingCorners:
|
||||
UIRectCorner.allCorners, cornerRadii: corners)
|
||||
} else if let arc = locus as? Arc {
|
||||
if arc.ellipse.rx == arc.ellipse.ry {
|
||||
return arcToPath(arc)
|
||||
}
|
||||
} else if let point = locus as? Point {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: CGFloat(point.x), y: CGFloat(point.y)))
|
||||
path.addLine(to: CGPoint(x: CGFloat(point.x), y: CGFloat(point.y)))
|
||||
return path
|
||||
} else if let line = locus as? Line {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: CGFloat(line.x1), y: CGFloat(line.y1)))
|
||||
path.addLine(to: CGPoint(x: CGFloat(line.x2), y: CGFloat(line.y2)))
|
||||
return path
|
||||
} else if let polygon = locus as? Polygon {
|
||||
let path = pointsToPath(polygon.points)
|
||||
path.close()
|
||||
return path
|
||||
} else if let polygon = locus as? Polyline {
|
||||
return pointsToPath(polygon.points)
|
||||
} else if let rect = locus as? Rect {
|
||||
return UIBezierPath(rect: rect.cgRect())
|
||||
} else if let circle = locus as? Circle {
|
||||
return UIBezierPath(ovalIn: circle.bounds().cgRect())
|
||||
} else if let path = locus as? Path {
|
||||
return toBezierPath(path)
|
||||
}
|
||||
fatalError("Unsupported locus: \(locus)")
|
||||
}
|
||||
|
||||
fileprivate class func arcToPath(_ arc: Arc) -> UIBezierPath {
|
||||
let shift = CGFloat(arc.shift)
|
||||
let end = shift + CGFloat(arc.extent)
|
||||
let ellipse = arc.ellipse
|
||||
let center = CGPoint(x: CGFloat(ellipse.cx), y: CGFloat(ellipse.cy))
|
||||
return UIBezierPath(arcCenter: center, radius: CGFloat(ellipse.rx), startAngle: shift, endAngle: end, clockwise: true)
|
||||
}
|
||||
|
||||
fileprivate class func pointsToPath(_ points: [Double]) -> UIBezierPath {
|
||||
let parts = stride(from: 0, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) }
|
||||
let path = UIBezierPath()
|
||||
var first = true
|
||||
for part in parts {
|
||||
let point = CGPoint(x: CGFloat(part[0]), y: CGFloat(part[1]))
|
||||
if (first) {
|
||||
path.move(to: point)
|
||||
first = false
|
||||
} else {
|
||||
path.addLine(to: point)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fileprivate class func toBezierPath(_ path: Path) -> UIBezierPath {
|
||||
let bezierPath = UIBezierPath()
|
||||
|
||||
var currentPoint: CGPoint?
|
||||
var cubicPoint: CGPoint?
|
||||
var quadrPoint: CGPoint?
|
||||
var initialPoint: CGPoint?
|
||||
|
||||
func M(_ x: Double, y: Double) {
|
||||
let point = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
bezierPath.move(to: point)
|
||||
setInitPoint(point)
|
||||
}
|
||||
|
||||
fileprivate class func toBezierPath(_ path: Path) -> UIBezierPath {
|
||||
let bezierPath = UIBezierPath()
|
||||
|
||||
var currentPoint: CGPoint?
|
||||
var cubicPoint: CGPoint?
|
||||
var quadrPoint: CGPoint?
|
||||
var initialPoint: CGPoint?
|
||||
|
||||
func M(_ x: Double, y: Double) {
|
||||
let point = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
bezierPath.move(to: point)
|
||||
setInitPoint(point)
|
||||
}
|
||||
|
||||
func m(_ x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
bezierPath.move(to: next)
|
||||
setInitPoint(next)
|
||||
} else {
|
||||
M(x, y: y)
|
||||
}
|
||||
}
|
||||
|
||||
func L(_ x: Double, y: Double) {
|
||||
lineTo(CGPoint(x: CGFloat(x), y: CGFloat(y)))
|
||||
}
|
||||
|
||||
func l(_ x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y))
|
||||
} else {
|
||||
L(x, y: y)
|
||||
}
|
||||
}
|
||||
|
||||
func H(_ x: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(x), y: CGFloat(cur.y)))
|
||||
}
|
||||
}
|
||||
|
||||
func h(_ x: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(cur.y)))
|
||||
}
|
||||
}
|
||||
|
||||
func V(_ y: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(cur.x), y: CGFloat(y)))
|
||||
}
|
||||
}
|
||||
|
||||
func v(_ y: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(cur.x), y: CGFloat(y) + cur.y))
|
||||
}
|
||||
}
|
||||
|
||||
func lineTo(_ p: CGPoint) {
|
||||
bezierPath.addLine(to: p)
|
||||
setPoint(p)
|
||||
}
|
||||
|
||||
func c(_ x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let endPoint = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
let controlPoint1 = CGPoint(x: CGFloat(x1) + cur.x, y: CGFloat(y1) + cur.y)
|
||||
let controlPoint2 = CGPoint(x: CGFloat(x2) + cur.x, y: CGFloat(y2) + cur.y)
|
||||
bezierPath.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
setCubicPoint(endPoint, cubic: controlPoint2)
|
||||
}
|
||||
}
|
||||
|
||||
func C(_ x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) {
|
||||
let endPoint = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
let controlPoint1 = CGPoint(x: CGFloat(x1), y: CGFloat(y1))
|
||||
let controlPoint2 = CGPoint(x: CGFloat(x2), y: CGFloat(y2))
|
||||
bezierPath.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
setCubicPoint(endPoint, cubic: controlPoint2)
|
||||
}
|
||||
|
||||
func s(_ x2: Double, y2: Double, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let nextCubic = CGPoint(x: CGFloat(x2) + cur.x, y: CGFloat(y2) + cur.y)
|
||||
let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
|
||||
var xy1: CGPoint?
|
||||
if let curCubicVal = cubicPoint {
|
||||
xy1 = CGPoint(x: CGFloat(2 * cur.x) - curCubicVal.x, y: CGFloat(2 * cur.y) - curCubicVal.y)
|
||||
} else {
|
||||
xy1 = cur
|
||||
}
|
||||
bezierPath.addCurve(to: next, controlPoint1: xy1!, controlPoint2: nextCubic)
|
||||
setCubicPoint(next, cubic: nextCubic)
|
||||
}
|
||||
}
|
||||
|
||||
func S(_ x2: Double, y2: Double, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let nextCubic = CGPoint(x: CGFloat(x2), y: CGFloat(y2))
|
||||
let next = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
var xy1: CGPoint?
|
||||
if let curCubicVal = cubicPoint {
|
||||
xy1 = CGPoint(x: CGFloat(2 * cur.x) - curCubicVal.x, y: CGFloat(2 * cur.y) - curCubicVal.y)
|
||||
} else {
|
||||
xy1 = cur
|
||||
}
|
||||
bezierPath.addCurve(to: next, controlPoint1: xy1!, controlPoint2: nextCubic)
|
||||
setCubicPoint(next, cubic: nextCubic)
|
||||
}
|
||||
}
|
||||
|
||||
func a(_ rx: Double, ry: Double, angle: Double, largeArc: Bool, sweep: Bool, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
A(rx, ry: ry, angle: angle, largeArc: largeArc, sweep: sweep, x: x + Double(cur.x), y: y + Double(cur.y))
|
||||
}
|
||||
}
|
||||
|
||||
func A(_ rx: Double, ry: Double, angle: Double, largeArc: Bool, sweep: Bool, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let x1 = Double(cur.x)
|
||||
let y1 = Double(cur.y)
|
||||
|
||||
// find arc center coordinates and points angles as per
|
||||
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
||||
let x1_ = cos(angle) * (x1 - x) / 2 + sin(angle) * (y1 - y) / 2;
|
||||
let y1_ = -1 * sin(angle) * (x1 - x) / 2 + cos(angle) * (y1 - y) / 2;
|
||||
// make sure the value under the root is positive
|
||||
let underroot = (rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_)
|
||||
/ (rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_);
|
||||
var bigRoot = (underroot > 0) ? sqrt(underroot) : 0;
|
||||
// TODO: Replace concrete number with 1e-2
|
||||
bigRoot = (bigRoot <= 0.01) ? 0 : bigRoot;
|
||||
let coef: Double = (sweep != largeArc) ? 1 : -1;
|
||||
let cx_ = coef * bigRoot * rx * y1_ / ry;
|
||||
let cy_ = -1 * coef * bigRoot * ry * x1_ / rx;
|
||||
let cx = (cos(angle) * cx_ - sin(angle) * cy_ + (x1 + x) / 2);
|
||||
let cy = (sin(angle) * cx_ + cos(angle) * cy_ + (y1 + y) / 2);
|
||||
let t1 = -1 * atan2(y1 - cy, x1 - cx);
|
||||
let t2 = atan2(y - cy, x - cx);
|
||||
var delta = -(t1 + t2);
|
||||
// recalculate delta depending on arc. Preserve rotation direction
|
||||
if (largeArc) {
|
||||
let sg = copysign(1.0, delta);
|
||||
if (abs(delta) < Double.pi) {
|
||||
delta = -1 * (sg * M_2_PI - delta);
|
||||
}
|
||||
} else {
|
||||
let sg = copysign(1.0, delta);
|
||||
if (abs(delta) > Double.pi) {
|
||||
delta = -1 * (sg * M_2_PI - delta);
|
||||
}
|
||||
}
|
||||
E(cx - rx, y: cy - ry, w: 2 * rx, h: 2 * ry, startAngle: t1, arcAngle: delta);
|
||||
setPoint(CGPoint(x: CGFloat(x), y: CGFloat(y)))
|
||||
}
|
||||
}
|
||||
|
||||
func E(_ x: Double, y: Double, w: Double, h: Double, startAngle: Double, arcAngle: Double) {
|
||||
// TODO: only circle now
|
||||
let extent = CGFloat(startAngle)
|
||||
let end = extent + CGFloat(arcAngle)
|
||||
let center = CGPoint(x: CGFloat(x + w / 2), y: CGFloat(y + h / 2))
|
||||
bezierPath.addArc(withCenter: center, radius: CGFloat(w / 2), startAngle: extent, endAngle: end, clockwise: true)
|
||||
}
|
||||
|
||||
func e(_ x: Double, y: Double, w: Double, h: Double, startAngle: Double, arcAngle: Double) {
|
||||
// TODO: only circle now
|
||||
if let cur = currentPoint {
|
||||
E(x + Double(cur.x), y: y + Double(cur.y), w: w, h: h, startAngle: startAngle, arcAngle: arcAngle)
|
||||
}
|
||||
}
|
||||
|
||||
func Z() {
|
||||
if let initPoint = initialPoint {
|
||||
lineTo(initPoint)
|
||||
}
|
||||
bezierPath.close()
|
||||
}
|
||||
|
||||
func setCubicPoint(_ p: CGPoint, cubic: CGPoint) {
|
||||
currentPoint = p
|
||||
cubicPoint = cubic
|
||||
quadrPoint = nil
|
||||
}
|
||||
|
||||
func setInitPoint(_ p: CGPoint) {
|
||||
setPoint(p)
|
||||
initialPoint = p
|
||||
}
|
||||
|
||||
func setPoint(_ p: CGPoint) {
|
||||
currentPoint = p
|
||||
cubicPoint = nil
|
||||
quadrPoint = nil
|
||||
}
|
||||
|
||||
// TODO: think about this
|
||||
for part in path.segments {
|
||||
var data = part.data
|
||||
switch part.type {
|
||||
case .M:
|
||||
M(data[0], y: data[1])
|
||||
data.removeSubrange(Range(uncheckedBounds: (lower: 0, upper: 2)))
|
||||
while data.count >= 2 {
|
||||
L(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .m:
|
||||
m(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
while data.count >= 2 {
|
||||
l(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .L:
|
||||
while data.count >= 2 {
|
||||
L(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .l:
|
||||
while data.count >= 2 {
|
||||
l(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .H:
|
||||
H(data[0])
|
||||
case .h:
|
||||
h(data[0])
|
||||
case .V:
|
||||
V(data[0])
|
||||
case .v:
|
||||
v(data[0])
|
||||
case .C:
|
||||
while data.count >= 6 {
|
||||
C(data[0], y1: data[1], x2: data[2], y2: data[3], x: data[4], y: data[5])
|
||||
data.removeSubrange((0 ..< 6))
|
||||
}
|
||||
case .c:
|
||||
while data.count >= 6 {
|
||||
c(data[0], y1: data[1], x2: data[2], y2: data[3], x: data[4], y: data[5])
|
||||
data.removeSubrange((0 ..< 6))
|
||||
}
|
||||
case .S:
|
||||
while data.count >= 4 {
|
||||
S(data[0], y2: data[1], x: data[2], y: data[3])
|
||||
data.removeSubrange((0 ..< 4))
|
||||
}
|
||||
case .s:
|
||||
while data.count >= 4 {
|
||||
s(data[0], y2: data[1], x: data[2], y: data[3])
|
||||
data.removeSubrange((0 ..< 4))
|
||||
}
|
||||
case .A:
|
||||
let flags = numToBools(data[3])
|
||||
A(data[0], ry: data[1], angle: data[2], largeArc: flags[0], sweep: flags[1], x: data[4], y: data[5])
|
||||
case .a:
|
||||
let flags = numToBools(data[3])
|
||||
a(data[0], ry: data[1], angle: data[2], largeArc: flags[0], sweep: flags[1], x: data[4], y: data[5])
|
||||
case .z:
|
||||
Z()
|
||||
default:
|
||||
fatalError("Unknown segment: \(part.type)")
|
||||
}
|
||||
}
|
||||
return bezierPath
|
||||
func m(_ x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
bezierPath.move(to: next)
|
||||
setInitPoint(next)
|
||||
} else {
|
||||
M(x, y: y)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class func numToBools(_ num: Double) -> [Bool] {
|
||||
let val: Int = Int(num);
|
||||
return [(val & 1) > 0, (val & 2) > 0];
|
||||
func L(_ x: Double, y: Double) {
|
||||
lineTo(CGPoint(x: CGFloat(x), y: CGFloat(y)))
|
||||
}
|
||||
|
||||
fileprivate class func newCGRect(_ rect: Rect) -> CGRect {
|
||||
return CGRect(x: CGFloat(rect.x), y: CGFloat(rect.y), width: CGFloat(rect.w), height: CGFloat(rect.h))
|
||||
func l(_ x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y))
|
||||
} else {
|
||||
L(x, y: y)
|
||||
}
|
||||
}
|
||||
|
||||
func H(_ x: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(x), y: CGFloat(cur.y)))
|
||||
}
|
||||
}
|
||||
|
||||
func h(_ x: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(cur.y)))
|
||||
}
|
||||
}
|
||||
|
||||
func V(_ y: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(cur.x), y: CGFloat(y)))
|
||||
}
|
||||
}
|
||||
|
||||
func v(_ y: Double) {
|
||||
if let cur = currentPoint {
|
||||
lineTo(CGPoint(x: CGFloat(cur.x), y: CGFloat(y) + cur.y))
|
||||
}
|
||||
}
|
||||
|
||||
func lineTo(_ p: CGPoint) {
|
||||
bezierPath.addLine(to: p)
|
||||
setPoint(p)
|
||||
}
|
||||
|
||||
func c(_ x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let endPoint = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
let controlPoint1 = CGPoint(x: CGFloat(x1) + cur.x, y: CGFloat(y1) + cur.y)
|
||||
let controlPoint2 = CGPoint(x: CGFloat(x2) + cur.x, y: CGFloat(y2) + cur.y)
|
||||
bezierPath.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
setCubicPoint(endPoint, cubic: controlPoint2)
|
||||
}
|
||||
}
|
||||
|
||||
func C(_ x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) {
|
||||
let endPoint = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
let controlPoint1 = CGPoint(x: CGFloat(x1), y: CGFloat(y1))
|
||||
let controlPoint2 = CGPoint(x: CGFloat(x2), y: CGFloat(y2))
|
||||
bezierPath.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
setCubicPoint(endPoint, cubic: controlPoint2)
|
||||
}
|
||||
|
||||
func s(_ x2: Double, y2: Double, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let nextCubic = CGPoint(x: CGFloat(x2) + cur.x, y: CGFloat(y2) + cur.y)
|
||||
let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
|
||||
var xy1: CGPoint?
|
||||
if let curCubicVal = cubicPoint {
|
||||
xy1 = CGPoint(x: CGFloat(2 * cur.x) - curCubicVal.x, y: CGFloat(2 * cur.y) - curCubicVal.y)
|
||||
} else {
|
||||
xy1 = cur
|
||||
}
|
||||
bezierPath.addCurve(to: next, controlPoint1: xy1!, controlPoint2: nextCubic)
|
||||
setCubicPoint(next, cubic: nextCubic)
|
||||
}
|
||||
}
|
||||
|
||||
func S(_ x2: Double, y2: Double, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let nextCubic = CGPoint(x: CGFloat(x2), y: CGFloat(y2))
|
||||
let next = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
var xy1: CGPoint?
|
||||
if let curCubicVal = cubicPoint {
|
||||
xy1 = CGPoint(x: CGFloat(2 * cur.x) - curCubicVal.x, y: CGFloat(2 * cur.y) - curCubicVal.y)
|
||||
} else {
|
||||
xy1 = cur
|
||||
}
|
||||
bezierPath.addCurve(to: next, controlPoint1: xy1!, controlPoint2: nextCubic)
|
||||
setCubicPoint(next, cubic: nextCubic)
|
||||
}
|
||||
}
|
||||
|
||||
func a(_ rx: Double, ry: Double, angle: Double, largeArc: Bool, sweep: Bool, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
A(rx, ry: ry, angle: angle, largeArc: largeArc, sweep: sweep, x: x + Double(cur.x), y: y + Double(cur.y))
|
||||
}
|
||||
}
|
||||
|
||||
func A(_ rx: Double, ry: Double, angle: Double, largeArc: Bool, sweep: Bool, x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let x1 = Double(cur.x)
|
||||
let y1 = Double(cur.y)
|
||||
|
||||
// find arc center coordinates and points angles as per
|
||||
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
||||
let x1_ = cos(angle) * (x1 - x) / 2 + sin(angle) * (y1 - y) / 2;
|
||||
let y1_ = -1 * sin(angle) * (x1 - x) / 2 + cos(angle) * (y1 - y) / 2;
|
||||
// make sure the value under the root is positive
|
||||
let underroot = (rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_)
|
||||
/ (rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_);
|
||||
var bigRoot = (underroot > 0) ? sqrt(underroot) : 0;
|
||||
// TODO: Replace concrete number with 1e-2
|
||||
bigRoot = (bigRoot <= 0.01) ? 0 : bigRoot;
|
||||
let coef: Double = (sweep != largeArc) ? 1 : -1;
|
||||
let cx_ = coef * bigRoot * rx * y1_ / ry;
|
||||
let cy_ = -1 * coef * bigRoot * ry * x1_ / rx;
|
||||
let cx = (cos(angle) * cx_ - sin(angle) * cy_ + (x1 + x) / 2);
|
||||
let cy = (sin(angle) * cx_ + cos(angle) * cy_ + (y1 + y) / 2);
|
||||
let t1 = -1 * atan2(y1 - cy, x1 - cx);
|
||||
let t2 = atan2(y - cy, x - cx);
|
||||
var delta = -(t1 + t2);
|
||||
// recalculate delta depending on arc. Preserve rotation direction
|
||||
if (largeArc) {
|
||||
let sg = copysign(1.0, delta);
|
||||
if (abs(delta) < Double.pi) {
|
||||
delta = -1 * (sg * M_2_PI - delta);
|
||||
}
|
||||
} else {
|
||||
let sg = copysign(1.0, delta);
|
||||
if (abs(delta) > Double.pi) {
|
||||
delta = -1 * (sg * M_2_PI - delta);
|
||||
}
|
||||
}
|
||||
E(cx - rx, y: cy - ry, w: 2 * rx, h: 2 * ry, startAngle: t1, arcAngle: delta);
|
||||
setPoint(CGPoint(x: CGFloat(x), y: CGFloat(y)))
|
||||
}
|
||||
}
|
||||
|
||||
func E(_ x: Double, y: Double, w: Double, h: Double, startAngle: Double, arcAngle: Double) {
|
||||
// TODO: only circle now
|
||||
let extent = CGFloat(startAngle)
|
||||
let end = extent + CGFloat(arcAngle)
|
||||
let center = CGPoint(x: CGFloat(x + w / 2), y: CGFloat(y + h / 2))
|
||||
bezierPath.addArc(withCenter: center, radius: CGFloat(w / 2), startAngle: extent, endAngle: end, clockwise: true)
|
||||
}
|
||||
|
||||
func e(_ x: Double, y: Double, w: Double, h: Double, startAngle: Double, arcAngle: Double) {
|
||||
// TODO: only circle now
|
||||
if let cur = currentPoint {
|
||||
E(x + Double(cur.x), y: y + Double(cur.y), w: w, h: h, startAngle: startAngle, arcAngle: arcAngle)
|
||||
}
|
||||
}
|
||||
|
||||
func Z() {
|
||||
if let initPoint = initialPoint {
|
||||
lineTo(initPoint)
|
||||
}
|
||||
bezierPath.close()
|
||||
}
|
||||
|
||||
func setCubicPoint(_ p: CGPoint, cubic: CGPoint) {
|
||||
currentPoint = p
|
||||
cubicPoint = cubic
|
||||
quadrPoint = nil
|
||||
}
|
||||
|
||||
func setInitPoint(_ p: CGPoint) {
|
||||
setPoint(p)
|
||||
initialPoint = p
|
||||
}
|
||||
|
||||
func setPoint(_ p: CGPoint) {
|
||||
currentPoint = p
|
||||
cubicPoint = nil
|
||||
quadrPoint = nil
|
||||
}
|
||||
|
||||
// TODO: think about this
|
||||
for part in path.segments {
|
||||
var data = part.data
|
||||
switch part.type {
|
||||
case .M:
|
||||
M(data[0], y: data[1])
|
||||
data.removeSubrange(Range(uncheckedBounds: (lower: 0, upper: 2)))
|
||||
while data.count >= 2 {
|
||||
L(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .m:
|
||||
m(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
while data.count >= 2 {
|
||||
l(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .L:
|
||||
while data.count >= 2 {
|
||||
L(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .l:
|
||||
while data.count >= 2 {
|
||||
l(data[0], y: data[1])
|
||||
data.removeSubrange((0 ..< 2))
|
||||
}
|
||||
case .H:
|
||||
H(data[0])
|
||||
case .h:
|
||||
h(data[0])
|
||||
case .V:
|
||||
V(data[0])
|
||||
case .v:
|
||||
v(data[0])
|
||||
case .C:
|
||||
while data.count >= 6 {
|
||||
C(data[0], y1: data[1], x2: data[2], y2: data[3], x: data[4], y: data[5])
|
||||
data.removeSubrange((0 ..< 6))
|
||||
}
|
||||
case .c:
|
||||
while data.count >= 6 {
|
||||
c(data[0], y1: data[1], x2: data[2], y2: data[3], x: data[4], y: data[5])
|
||||
data.removeSubrange((0 ..< 6))
|
||||
}
|
||||
case .S:
|
||||
while data.count >= 4 {
|
||||
S(data[0], y2: data[1], x: data[2], y: data[3])
|
||||
data.removeSubrange((0 ..< 4))
|
||||
}
|
||||
case .s:
|
||||
while data.count >= 4 {
|
||||
s(data[0], y2: data[1], x: data[2], y: data[3])
|
||||
data.removeSubrange((0 ..< 4))
|
||||
}
|
||||
case .A:
|
||||
let flags = numToBools(data[3])
|
||||
A(data[0], ry: data[1], angle: data[2], largeArc: flags[0], sweep: flags[1], x: data[4], y: data[5])
|
||||
case .a:
|
||||
let flags = numToBools(data[3])
|
||||
a(data[0], ry: data[1], angle: data[2], largeArc: flags[0], sweep: flags[1], x: data[4], y: data[5])
|
||||
case .z:
|
||||
Z()
|
||||
default:
|
||||
fatalError("Unknown segment: \(part.type)")
|
||||
}
|
||||
}
|
||||
return bezierPath
|
||||
}
|
||||
|
||||
fileprivate class func numToBools(_ num: Double) -> [Bool] {
|
||||
let val: Int = Int(num);
|
||||
return [(val & 1) > 0, (val & 2) > 0];
|
||||
}
|
||||
|
||||
fileprivate class func newCGRect(_ rect: Rect) -> CGRect {
|
||||
return CGRect(x: CGFloat(rect.x), y: CGFloat(rect.y), width: CGFloat(rect.w), height: CGFloat(rect.h))
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,127 +1,129 @@
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
class TextRenderer: NodeRenderer {
|
||||
weak var text: Text?
|
||||
weak var text: Text?
|
||||
|
||||
init(text: Text, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.text = text
|
||||
super.init(node: text, ctx: ctx, animationCache: animationCache)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
return text
|
||||
}
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
init(text: Text, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.text = text
|
||||
super.init(node: text, ctx: ctx, animationCache: animationCache)
|
||||
guard let text = text else {
|
||||
return
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
return text
|
||||
observe(text.textVar)
|
||||
observe(text.fontVar)
|
||||
observe(text.fillVar)
|
||||
observe(text.alignVar)
|
||||
observe(text.baselineVar)
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
guard let text = text else {
|
||||
return
|
||||
}
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
guard let text = text else {
|
||||
return
|
||||
}
|
||||
|
||||
observe(text.textVar)
|
||||
observe(text.fontVar)
|
||||
observe(text.fillVar)
|
||||
observe(text.alignVar)
|
||||
observe(text.baselineVar)
|
||||
let message = text.text
|
||||
let font = getMFont()
|
||||
// positive NSBaselineOffsetAttributeName values don't work, couldn't find why
|
||||
// for now move the rect itself
|
||||
if var color = text.fill as? Color {
|
||||
color = RenderUtils.applyOpacity(color, opacity: opacity)
|
||||
MGraphicsPushContext(ctx.cgContext!)
|
||||
message.draw(in: getBounds(font), withAttributes: [NSFontAttributeName: font,
|
||||
NSForegroundColorAttributeName: getTextColor(color)])
|
||||
MGraphicsPopContext()
|
||||
}
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let contains = node()?.bounds()?.cgRect().contains(location) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
guard let text = text else {
|
||||
return
|
||||
}
|
||||
|
||||
let message = text.text
|
||||
let font = getUIFont()
|
||||
// positive NSBaselineOffsetAttributeName values don't work, couldn't find why
|
||||
// for now move the rect itself
|
||||
if var color = text.fill as? Color {
|
||||
color = RenderUtils.applyOpacity(color, opacity: opacity)
|
||||
UIGraphicsPushContext(ctx.cgContext!)
|
||||
message.draw(in: getBounds(font), withAttributes: [NSFontAttributeName: font,
|
||||
NSForegroundColorAttributeName: getTextColor(color)])
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
if contains {
|
||||
return node()
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let contains = node()?.bounds()?.cgRect().contains(location) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if contains {
|
||||
return node()
|
||||
}
|
||||
|
||||
return .none
|
||||
return .none
|
||||
}
|
||||
|
||||
fileprivate func getMFont() -> MFont {
|
||||
guard let text = text else {
|
||||
return MFont.systemFont(ofSize: 18.0)
|
||||
}
|
||||
|
||||
fileprivate func getUIFont() -> UIFont {
|
||||
guard let text = text else {
|
||||
return UIFont.systemFont(ofSize: 18.0)
|
||||
}
|
||||
|
||||
if let textFont = text.font {
|
||||
if let customFont = RenderUtils.loadFont(name: textFont.name, size: textFont.size) {
|
||||
return customFont
|
||||
} else {
|
||||
return UIFont.systemFont(ofSize: CGFloat(textFont.size))
|
||||
}
|
||||
}
|
||||
return UIFont.systemFont(ofSize: UIFont.systemFontSize)
|
||||
if let textFont = text.font {
|
||||
if let customFont = RenderUtils.loadFont(name: textFont.name, size: textFont.size) {
|
||||
return customFont
|
||||
} else {
|
||||
return MFont.systemFont(ofSize: CGFloat(textFont.size))
|
||||
}
|
||||
}
|
||||
return MFont.systemFont(ofSize: MFont.systemFontSize)
|
||||
}
|
||||
|
||||
fileprivate func getBounds(_ font: MFont) -> CGRect {
|
||||
guard let text = text else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
fileprivate func getBounds(_ font: UIFont) -> CGRect {
|
||||
guard let text = text else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
let textAttributes = [NSFontAttributeName: font]
|
||||
let textSize = NSString(string: text.text).size(attributes: textAttributes)
|
||||
return CGRect(x: calculateAlignmentOffset(text, font: font),
|
||||
y: calculateBaselineOffset(text, font: font),
|
||||
width: CGFloat(textSize.width), height: CGFloat(textSize.height))
|
||||
let textAttributes = [NSFontAttributeName: font]
|
||||
let textSize = NSString(string: text.text).size(attributes: textAttributes)
|
||||
return CGRect(x: calculateAlignmentOffset(text, font: font),
|
||||
y: calculateBaselineOffset(text, font: font),
|
||||
width: CGFloat(textSize.width), height: CGFloat(textSize.height))
|
||||
}
|
||||
|
||||
fileprivate func calculateBaselineOffset(_ text: Text, font: MFont) -> CGFloat {
|
||||
var baselineOffset = CGFloat(0)
|
||||
switch text.baseline {
|
||||
case Baseline.alphabetic:
|
||||
baselineOffset = font.ascender
|
||||
case Baseline.bottom:
|
||||
baselineOffset = font.ascender - font.descender
|
||||
case Baseline.mid:
|
||||
baselineOffset = (font.ascender - font.descender) / 2
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
fileprivate func calculateBaselineOffset(_ text: Text, font: UIFont) -> CGFloat {
|
||||
var baselineOffset = CGFloat(0)
|
||||
switch text.baseline {
|
||||
case Baseline.alphabetic:
|
||||
baselineOffset = font.ascender
|
||||
case Baseline.bottom:
|
||||
baselineOffset = font.ascender - font.descender
|
||||
case Baseline.mid:
|
||||
baselineOffset = (font.ascender - font.descender) / 2
|
||||
default:
|
||||
break
|
||||
}
|
||||
return -baselineOffset
|
||||
return -baselineOffset
|
||||
}
|
||||
|
||||
fileprivate func calculateAlignmentOffset(_ text: Text, font: MFont) -> CGFloat {
|
||||
let textAttributes = [
|
||||
NSFontAttributeName: font
|
||||
]
|
||||
let textSize = NSString(string: text.text).size(attributes: textAttributes)
|
||||
var alignmentOffset = CGFloat(0)
|
||||
switch text.align {
|
||||
case Align.mid:
|
||||
alignmentOffset = textSize.width / 2
|
||||
case Align.max:
|
||||
alignmentOffset = textSize.width
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
fileprivate func calculateAlignmentOffset(_ text: Text, font: UIFont) -> CGFloat {
|
||||
let textAttributes = [
|
||||
NSFontAttributeName: font
|
||||
]
|
||||
let textSize = NSString(string: text.text).size(attributes: textAttributes)
|
||||
var alignmentOffset = CGFloat(0)
|
||||
switch text.align {
|
||||
case Align.mid:
|
||||
alignmentOffset = textSize.width / 2
|
||||
case Align.max:
|
||||
alignmentOffset = textSize.width
|
||||
default:
|
||||
break
|
||||
}
|
||||
return -alignmentOffset
|
||||
}
|
||||
|
||||
fileprivate func getTextColor(_ fill: Fill) -> UIColor {
|
||||
if let color = fill as? Color {
|
||||
return UIColor(cgColor: RenderUtils.mapColor(color))
|
||||
}
|
||||
return UIColor.black
|
||||
return -alignmentOffset
|
||||
}
|
||||
|
||||
fileprivate func getTextColor(_ fill: Fill) -> MColor {
|
||||
if let color = fill as? Color {
|
||||
return MColor(cgColor: RenderUtils.mapColor(color))
|
||||
}
|
||||
return MColor.black
|
||||
}
|
||||
}
|
||||
|
@ -1,186 +1,186 @@
|
||||
open class SVGConstants {
|
||||
|
||||
open static let colorList = [
|
||||
"aliceblue": 0xf0f8ff,
|
||||
"antiquewhite": 0xfaebd7,
|
||||
"aqua": 0x00ffff,
|
||||
"aquamarine": 0x7fffd4,
|
||||
"azure": 0xf0ffff,
|
||||
"beige": 0xf5f5dc,
|
||||
"bisque": 0xffe4c4,
|
||||
"black": 0x000000,
|
||||
"blanchedalmond": 0xffebcd,
|
||||
"blue": 0x0000ff,
|
||||
"blueviolet": 0x8a2be2,
|
||||
"brown": 0xa52a2a,
|
||||
"burlywood": 0xdeb887,
|
||||
"cadetblue": 0x5f9ea0,
|
||||
"chartreuse": 0x7fff00,
|
||||
"chocolate": 0xd2691e,
|
||||
"coral": 0xff7f50,
|
||||
"cornflowerblue": 0x6495ed,
|
||||
"cornsilk": 0xfff8dc,
|
||||
"crimson": 0xdc143c,
|
||||
"cyan": 0x00ffff,
|
||||
"darkblue": 0x00008b,
|
||||
"darkcyan": 0x008b8b,
|
||||
"darkgoldenrod": 0xb8860b,
|
||||
"darkgray": 0xa9a9a9,
|
||||
"darkgreen": 0x006400,
|
||||
"darkgrey": 0xa9a9a9,
|
||||
"darkkhaki": 0xbdb76b,
|
||||
"darkmagenta": 0x8b008b,
|
||||
"darkolivegreen": 0x556b2f,
|
||||
"darkorange": 0xff8c00,
|
||||
"darkorchid": 0x9932cc,
|
||||
"darkred": 0x8b0000,
|
||||
"darksalmon": 0xe9967a,
|
||||
"darkseagreen": 0x8fbc8f,
|
||||
"darkslateblue": 0x483d8b,
|
||||
"darkslategray": 0x2f4f4f,
|
||||
"darkslategrey": 0x2f4f4f,
|
||||
"darkturquoise": 0x00ced1,
|
||||
"darkviolet": 0x9400d3,
|
||||
"deeppink": 0xff1493,
|
||||
"deepskyblue": 0x00bfff,
|
||||
"dimgray": 0x696969,
|
||||
"dimgrey": 0x696969,
|
||||
"dodgerblue": 0x1e90ff,
|
||||
"firebrick": 0xb22222,
|
||||
"floralwhite": 0xfffaf0,
|
||||
"forestgreen": 0x228b22,
|
||||
"fuchsia": 0xff00ff,
|
||||
"gainsboro": 0xdcdcdc,
|
||||
"ghostwhite": 0xf8f8ff,
|
||||
"gold": 0xffd700,
|
||||
"goldenrod": 0xdaa520,
|
||||
"gray": 0x808080,
|
||||
"green": 0x008000,
|
||||
"greenyellow": 0xadff2f,
|
||||
"grey": 0x808080,
|
||||
"honeydew": 0xf0fff0,
|
||||
"hotpink": 0xff69b4,
|
||||
"indianred": 0xcd5c5c,
|
||||
"indigo": 0x4b0082,
|
||||
"ivory": 0xfffff0,
|
||||
"khaki": 0xf0e68c,
|
||||
"lavender": 0xe6e6fa,
|
||||
"lavenderblush": 0xfff0f5,
|
||||
"lawngreen": 0x7cfc00,
|
||||
"lemonchiffon": 0xfffacd,
|
||||
"lightblue": 0xadd8e6,
|
||||
"lightcoral": 0xf08080,
|
||||
"lightcyan": 0xe0ffff,
|
||||
"lightgoldenrodyellow": 0xfafad2,
|
||||
"lightgray": 0xd3d3d3,
|
||||
"lightgreen": 0x90ee90,
|
||||
"lightgrey": 0xd3d3d3,
|
||||
"lightpink": 0xffb6c1,
|
||||
"lightsalmon": 0xffa07a,
|
||||
"lightseagreen": 0x20b2aa,
|
||||
"lightskyblue": 0x87cefa,
|
||||
"lightslategray": 0x778899,
|
||||
"lightslategrey": 0x778899,
|
||||
"lightsteelblue": 0xb0c4de,
|
||||
"lightyellow": 0xffffe0,
|
||||
"lime": 0x00ff00,
|
||||
"limegreen": 0x32cd32,
|
||||
"linen": 0xfaf0e6,
|
||||
"magenta": 0xff00ff,
|
||||
"maroon": 0x800000,
|
||||
"mediumaquamarine": 0x66cdaa,
|
||||
"mediumblue": 0x0000cd,
|
||||
"mediumorchid": 0xba55d3,
|
||||
"mediumpurple": 0x9370db,
|
||||
"mediumseagreen": 0x3cb371,
|
||||
"mediumslateblue": 0x7b68ee,
|
||||
"mediumspringgreen": 0x00fa9a,
|
||||
"mediumturquoise": 0x48d1cc,
|
||||
"mediumvioletred": 0xc71585,
|
||||
"midnightblue": 0x191970,
|
||||
"mintcream": 0xf5fffa,
|
||||
"mistyrose": 0xffe4e1,
|
||||
"moccasin": 0xffe4b5,
|
||||
"navajowhite": 0xffdead,
|
||||
"navy": 0x000080,
|
||||
"oldlace": 0xfdf5e6,
|
||||
"olive": 0x808000,
|
||||
"olivedrab": 0x6b8e23,
|
||||
"orange": 0xffa500,
|
||||
"orangered": 0xff4500,
|
||||
"orchid": 0xda70d6,
|
||||
"palegoldenrod": 0xeee8aa,
|
||||
"palegreen": 0x98fb98,
|
||||
"paleturquoise": 0xafeeee,
|
||||
"palevioletred": 0xdb7093,
|
||||
"papayawhip": 0xffefd5,
|
||||
"peachpuff": 0xffdab9,
|
||||
"peru": 0xcd853f,
|
||||
"pink": 0xffc0cb,
|
||||
"plum": 0xdda0dd,
|
||||
"powderblue": 0xb0e0e6,
|
||||
"purple": 0x800080,
|
||||
"rebeccapurple": 0x663399,
|
||||
"red": 0xff0000,
|
||||
"rosybrown": 0xbc8f8f,
|
||||
"royalblue": 0x4169e1,
|
||||
"saddlebrown": 0x8b4513,
|
||||
"salmon": 0xfa8072,
|
||||
"sandybrown": 0xf4a460,
|
||||
"seagreen": 0x2e8b57,
|
||||
"seashell": 0xfff5ee,
|
||||
"sienna": 0xa0522d,
|
||||
"silver": 0xc0c0c0,
|
||||
"skyblue": 0x87ceeb,
|
||||
"slateblue": 0x6a5acd,
|
||||
"slategray": 0x708090,
|
||||
"slategrey": 0x708090,
|
||||
"snow": 0xfffafa,
|
||||
"springgreen": 0x00ff7f,
|
||||
"steelblue": 0x4682b4,
|
||||
"tan": 0xd2b48c,
|
||||
"teal": 0x008080,
|
||||
"thistle": 0xd8bfd8,
|
||||
"tomato": 0xff6347,
|
||||
"turquoise": 0x40e0d0,
|
||||
"violet": 0xee82ee,
|
||||
"wheat": 0xf5deb3,
|
||||
"white": 0xffffff,
|
||||
"whitesmoke": 0xf5f5f5,
|
||||
"yellow": 0xffff00,
|
||||
"yellowgreen": 0x9acd32
|
||||
]
|
||||
|
||||
open static let moveToAbsolute = "M"
|
||||
open static let moveToRelative = "m"
|
||||
open static let lineToAbsolute = "L"
|
||||
open static let lineToRelative = "l"
|
||||
open static let lineHorizontalAbsolute = "H"
|
||||
open static let lineHorizontalRelative = "h"
|
||||
open static let lineVerticalAbsolute = "V"
|
||||
open static let lineVerticalRelative = "v"
|
||||
open static let curveToAbsolute = "C"
|
||||
open static let curveToRelative = "c"
|
||||
open static let smoothCurveToAbsolute = "S"
|
||||
open static let smoothCurveToRelative = "s"
|
||||
open static let closePathAbsolute = "Z"
|
||||
open static let closePathRelative = "z"
|
||||
|
||||
open static let pathCommands = [
|
||||
moveToAbsolute,
|
||||
moveToRelative,
|
||||
lineToAbsolute,
|
||||
lineToRelative,
|
||||
lineHorizontalAbsolute,
|
||||
lineHorizontalRelative,
|
||||
lineVerticalAbsolute,
|
||||
lineVerticalRelative,
|
||||
curveToAbsolute,
|
||||
curveToRelative,
|
||||
smoothCurveToAbsolute,
|
||||
smoothCurveToRelative,
|
||||
closePathAbsolute,
|
||||
closePathRelative
|
||||
]
|
||||
|
||||
|
||||
open static let colorList = [
|
||||
"aliceblue": 0xf0f8ff,
|
||||
"antiquewhite": 0xfaebd7,
|
||||
"aqua": 0x00ffff,
|
||||
"aquamarine": 0x7fffd4,
|
||||
"azure": 0xf0ffff,
|
||||
"beige": 0xf5f5dc,
|
||||
"bisque": 0xffe4c4,
|
||||
"black": 0x000000,
|
||||
"blanchedalmond": 0xffebcd,
|
||||
"blue": 0x0000ff,
|
||||
"blueviolet": 0x8a2be2,
|
||||
"brown": 0xa52a2a,
|
||||
"burlywood": 0xdeb887,
|
||||
"cadetblue": 0x5f9ea0,
|
||||
"chartreuse": 0x7fff00,
|
||||
"chocolate": 0xd2691e,
|
||||
"coral": 0xff7f50,
|
||||
"cornflowerblue": 0x6495ed,
|
||||
"cornsilk": 0xfff8dc,
|
||||
"crimson": 0xdc143c,
|
||||
"cyan": 0x00ffff,
|
||||
"darkblue": 0x00008b,
|
||||
"darkcyan": 0x008b8b,
|
||||
"darkgoldenrod": 0xb8860b,
|
||||
"darkgray": 0xa9a9a9,
|
||||
"darkgreen": 0x006400,
|
||||
"darkgrey": 0xa9a9a9,
|
||||
"darkkhaki": 0xbdb76b,
|
||||
"darkmagenta": 0x8b008b,
|
||||
"darkolivegreen": 0x556b2f,
|
||||
"darkorange": 0xff8c00,
|
||||
"darkorchid": 0x9932cc,
|
||||
"darkred": 0x8b0000,
|
||||
"darksalmon": 0xe9967a,
|
||||
"darkseagreen": 0x8fbc8f,
|
||||
"darkslateblue": 0x483d8b,
|
||||
"darkslategray": 0x2f4f4f,
|
||||
"darkslategrey": 0x2f4f4f,
|
||||
"darkturquoise": 0x00ced1,
|
||||
"darkviolet": 0x9400d3,
|
||||
"deeppink": 0xff1493,
|
||||
"deepskyblue": 0x00bfff,
|
||||
"dimgray": 0x696969,
|
||||
"dimgrey": 0x696969,
|
||||
"dodgerblue": 0x1e90ff,
|
||||
"firebrick": 0xb22222,
|
||||
"floralwhite": 0xfffaf0,
|
||||
"forestgreen": 0x228b22,
|
||||
"fuchsia": 0xff00ff,
|
||||
"gainsboro": 0xdcdcdc,
|
||||
"ghostwhite": 0xf8f8ff,
|
||||
"gold": 0xffd700,
|
||||
"goldenrod": 0xdaa520,
|
||||
"gray": 0x808080,
|
||||
"green": 0x008000,
|
||||
"greenyellow": 0xadff2f,
|
||||
"grey": 0x808080,
|
||||
"honeydew": 0xf0fff0,
|
||||
"hotpink": 0xff69b4,
|
||||
"indianred": 0xcd5c5c,
|
||||
"indigo": 0x4b0082,
|
||||
"ivory": 0xfffff0,
|
||||
"khaki": 0xf0e68c,
|
||||
"lavender": 0xe6e6fa,
|
||||
"lavenderblush": 0xfff0f5,
|
||||
"lawngreen": 0x7cfc00,
|
||||
"lemonchiffon": 0xfffacd,
|
||||
"lightblue": 0xadd8e6,
|
||||
"lightcoral": 0xf08080,
|
||||
"lightcyan": 0xe0ffff,
|
||||
"lightgoldenrodyellow": 0xfafad2,
|
||||
"lightgray": 0xd3d3d3,
|
||||
"lightgreen": 0x90ee90,
|
||||
"lightgrey": 0xd3d3d3,
|
||||
"lightpink": 0xffb6c1,
|
||||
"lightsalmon": 0xffa07a,
|
||||
"lightseagreen": 0x20b2aa,
|
||||
"lightskyblue": 0x87cefa,
|
||||
"lightslategray": 0x778899,
|
||||
"lightslategrey": 0x778899,
|
||||
"lightsteelblue": 0xb0c4de,
|
||||
"lightyellow": 0xffffe0,
|
||||
"lime": 0x00ff00,
|
||||
"limegreen": 0x32cd32,
|
||||
"linen": 0xfaf0e6,
|
||||
"magenta": 0xff00ff,
|
||||
"maroon": 0x800000,
|
||||
"mediumaquamarine": 0x66cdaa,
|
||||
"mediumblue": 0x0000cd,
|
||||
"mediumorchid": 0xba55d3,
|
||||
"mediumpurple": 0x9370db,
|
||||
"mediumseagreen": 0x3cb371,
|
||||
"mediumslateblue": 0x7b68ee,
|
||||
"mediumspringgreen": 0x00fa9a,
|
||||
"mediumturquoise": 0x48d1cc,
|
||||
"mediumvioletred": 0xc71585,
|
||||
"midnightblue": 0x191970,
|
||||
"mintcream": 0xf5fffa,
|
||||
"mistyrose": 0xffe4e1,
|
||||
"moccasin": 0xffe4b5,
|
||||
"navajowhite": 0xffdead,
|
||||
"navy": 0x000080,
|
||||
"oldlace": 0xfdf5e6,
|
||||
"olive": 0x808000,
|
||||
"olivedrab": 0x6b8e23,
|
||||
"orange": 0xffa500,
|
||||
"orangered": 0xff4500,
|
||||
"orchid": 0xda70d6,
|
||||
"palegoldenrod": 0xeee8aa,
|
||||
"palegreen": 0x98fb98,
|
||||
"paleturquoise": 0xafeeee,
|
||||
"palevioletred": 0xdb7093,
|
||||
"papayawhip": 0xffefd5,
|
||||
"peachpuff": 0xffdab9,
|
||||
"peru": 0xcd853f,
|
||||
"pink": 0xffc0cb,
|
||||
"plum": 0xdda0dd,
|
||||
"powderblue": 0xb0e0e6,
|
||||
"purple": 0x800080,
|
||||
"rebeccapurple": 0x663399,
|
||||
"red": 0xff0000,
|
||||
"rosybrown": 0xbc8f8f,
|
||||
"royalblue": 0x4169e1,
|
||||
"saddlebrown": 0x8b4513,
|
||||
"salmon": 0xfa8072,
|
||||
"sandybrown": 0xf4a460,
|
||||
"seagreen": 0x2e8b57,
|
||||
"seashell": 0xfff5ee,
|
||||
"sienna": 0xa0522d,
|
||||
"silver": 0xc0c0c0,
|
||||
"skyblue": 0x87ceeb,
|
||||
"slateblue": 0x6a5acd,
|
||||
"slategray": 0x708090,
|
||||
"slategrey": 0x708090,
|
||||
"snow": 0xfffafa,
|
||||
"springgreen": 0x00ff7f,
|
||||
"steelblue": 0x4682b4,
|
||||
"tan": 0xd2b48c,
|
||||
"teal": 0x008080,
|
||||
"thistle": 0xd8bfd8,
|
||||
"tomato": 0xff6347,
|
||||
"turquoise": 0x40e0d0,
|
||||
"violet": 0xee82ee,
|
||||
"wheat": 0xf5deb3,
|
||||
"white": 0xffffff,
|
||||
"whitesmoke": 0xf5f5f5,
|
||||
"yellow": 0xffff00,
|
||||
"yellowgreen": 0x9acd32
|
||||
]
|
||||
|
||||
open static let moveToAbsolute = "M"
|
||||
open static let moveToRelative = "m"
|
||||
open static let lineToAbsolute = "L"
|
||||
open static let lineToRelative = "l"
|
||||
open static let lineHorizontalAbsolute = "H"
|
||||
open static let lineHorizontalRelative = "h"
|
||||
open static let lineVerticalAbsolute = "V"
|
||||
open static let lineVerticalRelative = "v"
|
||||
open static let curveToAbsolute = "C"
|
||||
open static let curveToRelative = "c"
|
||||
open static let smoothCurveToAbsolute = "S"
|
||||
open static let smoothCurveToRelative = "s"
|
||||
open static let closePathAbsolute = "Z"
|
||||
open static let closePathRelative = "z"
|
||||
|
||||
open static let pathCommands = [
|
||||
moveToAbsolute,
|
||||
moveToRelative,
|
||||
lineToAbsolute,
|
||||
lineToRelative,
|
||||
lineHorizontalAbsolute,
|
||||
lineHorizontalRelative,
|
||||
lineVerticalAbsolute,
|
||||
lineVerticalRelative,
|
||||
curveToAbsolute,
|
||||
curveToRelative,
|
||||
smoothCurveToAbsolute,
|
||||
smoothCurveToRelative,
|
||||
closePathAbsolute,
|
||||
closePathRelative
|
||||
]
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,59 +1,59 @@
|
||||
import Foundation
|
||||
|
||||
class SVGParserRegexHelper {
|
||||
|
||||
fileprivate static let transformAttributePattern = "([a-z]+)\\(((\\-?\\d+\\.?\\d*e?\\-?\\d*\\s*,?\\s*)+)\\)"
|
||||
fileprivate static let transformPattern = "\\-?\\d+\\.?\\d*e?\\-?\\d*"
|
||||
fileprivate static let textElementPattern = "<text.*?>((?s:.*))<\\/text>"
|
||||
fileprivate static let maskIdenitifierPattern = "url\\(#((?s:.*))\\)"
|
||||
|
||||
fileprivate static var transformMatcher: NSRegularExpression?
|
||||
fileprivate static var transformAttributeMatcher: NSRegularExpression?
|
||||
fileprivate static var textElementMatcher: NSRegularExpression?
|
||||
fileprivate static var maskIdenitifierMatcher: NSRegularExpression?
|
||||
|
||||
class func getTransformAttributeMatcher() -> NSRegularExpression? {
|
||||
if self.transformAttributeMatcher == nil {
|
||||
do {
|
||||
self.transformAttributeMatcher = try NSRegularExpression(pattern: transformAttributePattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
return self.transformAttributeMatcher
|
||||
|
||||
fileprivate static let transformAttributePattern = "([a-z]+)\\(((\\-?\\d+\\.?\\d*e?\\-?\\d*\\s*,?\\s*)+)\\)"
|
||||
fileprivate static let transformPattern = "\\-?\\d+\\.?\\d*e?\\-?\\d*"
|
||||
fileprivate static let textElementPattern = "<text.*?>((?s:.*))<\\/text>"
|
||||
fileprivate static let maskIdenitifierPattern = "url\\(#((?s:.*))\\)"
|
||||
|
||||
fileprivate static var transformMatcher: NSRegularExpression?
|
||||
fileprivate static var transformAttributeMatcher: NSRegularExpression?
|
||||
fileprivate static var textElementMatcher: NSRegularExpression?
|
||||
fileprivate static var maskIdenitifierMatcher: NSRegularExpression?
|
||||
|
||||
class func getTransformAttributeMatcher() -> NSRegularExpression? {
|
||||
if self.transformAttributeMatcher == nil {
|
||||
do {
|
||||
self.transformAttributeMatcher = try NSRegularExpression(pattern: transformAttributePattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class func getTransformMatcher() -> NSRegularExpression? {
|
||||
if self.transformMatcher == nil {
|
||||
do {
|
||||
self.transformMatcher = try NSRegularExpression(pattern: transformPattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
return self.transformMatcher
|
||||
return self.transformAttributeMatcher
|
||||
}
|
||||
|
||||
class func getTransformMatcher() -> NSRegularExpression? {
|
||||
if self.transformMatcher == nil {
|
||||
do {
|
||||
self.transformMatcher = try NSRegularExpression(pattern: transformPattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class func getTextElementMatcher() -> NSRegularExpression? {
|
||||
if self.textElementMatcher == nil {
|
||||
do {
|
||||
self.textElementMatcher = try NSRegularExpression(pattern: textElementPattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
return self.textElementMatcher
|
||||
return self.transformMatcher
|
||||
}
|
||||
|
||||
class func getTextElementMatcher() -> NSRegularExpression? {
|
||||
if self.textElementMatcher == nil {
|
||||
do {
|
||||
self.textElementMatcher = try NSRegularExpression(pattern: textElementPattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class func getMaskIdenitifierMatcher() -> NSRegularExpression? {
|
||||
if self.maskIdenitifierMatcher == nil {
|
||||
do {
|
||||
self.maskIdenitifierMatcher = try NSRegularExpression(pattern: maskIdenitifierPattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
return self.maskIdenitifierMatcher
|
||||
return self.textElementMatcher
|
||||
}
|
||||
|
||||
class func getMaskIdenitifierMatcher() -> NSRegularExpression? {
|
||||
if self.maskIdenitifierMatcher == nil {
|
||||
do {
|
||||
self.maskIdenitifierMatcher = try NSRegularExpression(pattern: maskIdenitifierPattern, options: .caseInsensitive)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return self.maskIdenitifierMatcher
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,180 +1,182 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class SVGView: MacawView {
|
||||
|
||||
fileprivate let rootNode = Group()
|
||||
fileprivate var svgNode: Node?
|
||||
|
||||
@IBInspectable open var fileName: String? {
|
||||
didSet {
|
||||
parseSVG()
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
public init(node: Node = Group(), frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
svgNode = node
|
||||
}
|
||||
|
||||
override public init?(node: Node = Group(), coder aDecoder: NSCoder) {
|
||||
super.init(node: Group(), coder: aDecoder)
|
||||
svgNode = node
|
||||
}
|
||||
|
||||
required public convenience init?(coder aDecoder: NSCoder) {
|
||||
self.init(node: Group(), coder: aDecoder)
|
||||
}
|
||||
|
||||
open override var contentMode: UIViewContentMode {
|
||||
didSet {
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
fileprivate let rootNode = Group()
|
||||
fileprivate var svgNode: Node?
|
||||
|
||||
@IBInspectable open var fileName: String? {
|
||||
didSet {
|
||||
parseSVG()
|
||||
render()
|
||||
render()
|
||||
}
|
||||
|
||||
fileprivate func parseSVG() {
|
||||
svgNode = try? SVGParser.parse(path: fileName ?? "")
|
||||
}
|
||||
|
||||
fileprivate func render() {
|
||||
guard let svgNode = self.svgNode else {
|
||||
return
|
||||
}
|
||||
let viewBounds = self.bounds
|
||||
if let nodeBounds = svgNode.bounds()?.cgRect() {
|
||||
let svgWidth = nodeBounds.origin.x + nodeBounds.width
|
||||
let svgHeight = nodeBounds.origin.y + nodeBounds.height
|
||||
|
||||
let viewAspectRatio = viewBounds.width / viewBounds.height
|
||||
let svgAspectRatio = svgWidth / svgHeight
|
||||
|
||||
let scaleX = viewBounds.width / svgWidth
|
||||
let scaleY = viewBounds.height / svgHeight
|
||||
|
||||
switch self.contentMode {
|
||||
case .scaleToFill:
|
||||
svgNode.place = Transform.scale(
|
||||
sx: Double(scaleX),
|
||||
sy: Double(scaleY)
|
||||
)
|
||||
case .scaleAspectFill:
|
||||
let scaleX, scaleY: CGFloat
|
||||
if viewAspectRatio > svgAspectRatio {
|
||||
scaleX = viewBounds.width / svgWidth
|
||||
scaleY = viewBounds.width / (svgWidth / svgAspectRatio)
|
||||
} else {
|
||||
scaleX = viewBounds.height / (svgHeight / svgAspectRatio)
|
||||
scaleY = viewBounds.height / svgHeight
|
||||
}
|
||||
}
|
||||
|
||||
public init(node: Node = Group(), frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
svgNode = node
|
||||
}
|
||||
|
||||
override public init?(node: Node = Group(), coder aDecoder: NSCoder) {
|
||||
super.init(node: Group(), coder: aDecoder)
|
||||
svgNode = node
|
||||
}
|
||||
|
||||
required public convenience init?(coder aDecoder: NSCoder) {
|
||||
self.init(node: Group(), coder: aDecoder)
|
||||
}
|
||||
|
||||
open override var contentMode: UIViewContentMode {
|
||||
didSet {
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
let calculatedWidth = svgWidth * scaleX
|
||||
let calculatedHeight = svgHeight * scaleY
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.width / 2 - calculatedWidth / 2).doubleValue,
|
||||
dy: (viewBounds.height / 2 - calculatedHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleX.doubleValue,
|
||||
sy: scaleX.doubleValue
|
||||
)
|
||||
case .scaleAspectFit:
|
||||
let calculatedXWidth = scaleX * svgWidth
|
||||
let calculatedXHeight = scaleX * svgHeight
|
||||
let calculatedYWidth = scaleY * svgWidth
|
||||
let calculatedYHeight = scaleY * svgHeight
|
||||
|
||||
render()
|
||||
}
|
||||
|
||||
fileprivate func parseSVG() {
|
||||
svgNode = try? SVGParser.parse(path: fileName ?? "")
|
||||
}
|
||||
|
||||
fileprivate func render() {
|
||||
guard let svgNode = self.svgNode else {
|
||||
return
|
||||
if calculatedXWidth <= viewBounds.width && calculatedXHeight <= viewBounds.height {
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.midX - calculatedXWidth / 2).doubleValue,
|
||||
dy: (viewBounds.midY - calculatedXHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleX.doubleValue,
|
||||
sy: scaleX.doubleValue
|
||||
)
|
||||
} else if calculatedYWidth <= viewBounds.width && calculatedYHeight <= viewBounds.height {
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.midX - calculatedYWidth / 2).doubleValue,
|
||||
dy: (viewBounds.midY - calculatedYHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleY.doubleValue,
|
||||
sy: scaleY.doubleValue
|
||||
)
|
||||
}
|
||||
let viewBounds = self.bounds
|
||||
if let nodeBounds = svgNode.bounds()?.cgRect() {
|
||||
let svgWidth = nodeBounds.origin.x + nodeBounds.width
|
||||
let svgHeight = nodeBounds.origin.y + nodeBounds.height
|
||||
|
||||
let viewAspectRatio = viewBounds.width / viewBounds.height
|
||||
let svgAspectRatio = svgWidth / svgHeight
|
||||
|
||||
let scaleX = viewBounds.width / svgWidth
|
||||
let scaleY = viewBounds.height / svgHeight
|
||||
|
||||
switch self.contentMode {
|
||||
case .scaleToFill:
|
||||
svgNode.place = Transform.scale(
|
||||
sx: Double(scaleX),
|
||||
sy: Double(scaleY)
|
||||
)
|
||||
case .scaleAspectFill:
|
||||
let scaleX, scaleY: CGFloat
|
||||
if viewAspectRatio > svgAspectRatio {
|
||||
scaleX = viewBounds.width / svgWidth
|
||||
scaleY = viewBounds.width / (svgWidth / svgAspectRatio)
|
||||
} else {
|
||||
scaleX = viewBounds.height / (svgHeight / svgAspectRatio)
|
||||
scaleY = viewBounds.height / svgHeight
|
||||
}
|
||||
let calculatedWidth = svgWidth * scaleX
|
||||
let calculatedHeight = svgHeight * scaleY
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.width / 2 - calculatedWidth / 2).doubleValue,
|
||||
dy: (viewBounds.height / 2 - calculatedHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleX.doubleValue,
|
||||
sy: scaleX.doubleValue
|
||||
)
|
||||
case .scaleAspectFit:
|
||||
let calculatedXWidth = scaleX * svgWidth
|
||||
let calculatedXHeight = scaleX * svgHeight
|
||||
let calculatedYWidth = scaleY * svgWidth
|
||||
let calculatedYHeight = scaleY * svgHeight
|
||||
|
||||
if calculatedXWidth <= viewBounds.width && calculatedXHeight <= viewBounds.height {
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.midX - calculatedXWidth / 2).doubleValue,
|
||||
dy: (viewBounds.midY - calculatedXHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleX.doubleValue,
|
||||
sy: scaleX.doubleValue
|
||||
)
|
||||
} else if calculatedYWidth <= viewBounds.width && calculatedYHeight <= viewBounds.height {
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.midX - calculatedYWidth / 2).doubleValue,
|
||||
dy: (viewBounds.midY - calculatedYHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleY.doubleValue,
|
||||
sy: scaleY.doubleValue
|
||||
)
|
||||
}
|
||||
case .center:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .top:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: 0
|
||||
)
|
||||
case .bottom:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .left:
|
||||
svgNode.place = Transform.move(
|
||||
dx: 0,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .right:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .topLeft:
|
||||
break
|
||||
case .topRight:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: 0
|
||||
)
|
||||
case .bottomLeft:
|
||||
svgNode.place = Transform.move(
|
||||
dx: 0,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .bottomRight:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .redraw:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rootNode.contents = [svgNode]
|
||||
self.node = rootNode
|
||||
}
|
||||
|
||||
fileprivate func getMidX(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
let viewMidX = viewBounds.midX
|
||||
let nodeMidX = nodeBounds.midX + nodeBounds.origin.x
|
||||
return viewMidX - nodeMidX
|
||||
}
|
||||
|
||||
fileprivate func getMidY(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
let viewMidY = viewBounds.midY
|
||||
let nodeMidY = nodeBounds.midY + nodeBounds.origin.y
|
||||
return viewMidY - nodeMidY
|
||||
}
|
||||
|
||||
fileprivate func getBottom(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
return viewBounds.maxY - nodeBounds.maxY + nodeBounds.origin.y
|
||||
}
|
||||
|
||||
fileprivate func getRight(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
return viewBounds.maxX - nodeBounds.maxX + nodeBounds.origin.x
|
||||
case .center:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .top:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: 0
|
||||
)
|
||||
case .bottom:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .left:
|
||||
svgNode.place = Transform.move(
|
||||
dx: 0,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .right:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .topLeft:
|
||||
break
|
||||
case .topRight:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: 0
|
||||
)
|
||||
case .bottomLeft:
|
||||
svgNode.place = Transform.move(
|
||||
dx: 0,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .bottomRight:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .redraw:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rootNode.contents = [svgNode]
|
||||
self.node = rootNode
|
||||
}
|
||||
|
||||
fileprivate func getMidX(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
let viewMidX = viewBounds.midX
|
||||
let nodeMidX = nodeBounds.midX + nodeBounds.origin.x
|
||||
return viewMidX - nodeMidX
|
||||
}
|
||||
|
||||
fileprivate func getMidY(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
let viewMidY = viewBounds.midY
|
||||
let nodeMidY = nodeBounds.midY + nodeBounds.origin.y
|
||||
return viewMidY - nodeMidY
|
||||
}
|
||||
|
||||
fileprivate func getBottom(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
return viewBounds.maxY - nodeBounds.maxY + nodeBounds.origin.y
|
||||
}
|
||||
|
||||
fileprivate func getRight(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
return viewBounds.maxX - nodeBounds.maxX + nodeBounds.origin.x
|
||||
}
|
||||
|
||||
}
|
||||
|
264
Source/thirdparty/CAAnimationClosure.swift
vendored
264
Source/thirdparty/CAAnimationClosure.swift
vendored
@ -10,150 +10,150 @@ import QuartzCore
|
||||
|
||||
/// CAAnimation Delegation class implementation
|
||||
class CAAnimationDelegateImpl:NSObject, CAAnimationDelegate {
|
||||
/// start: A block (closure) object to be executed when the animation starts. This block has no return value and takes no argument.
|
||||
var start: (() -> Void)?
|
||||
|
||||
/// completion: A block (closure) object to be executed when the animation ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished.
|
||||
var completion: ((Bool) -> Void)?
|
||||
|
||||
/// startTime: animation start date
|
||||
fileprivate var startTime: Date!
|
||||
fileprivate var animationDuration: TimeInterval!
|
||||
fileprivate var animatingTimer: Timer!
|
||||
|
||||
/// animating: A block (closure) object to be executed when the animation is animating. This block has no return value and takes a single CGFloat argument that indicates the progress of the animation (From 0 ..< 1)
|
||||
var animating: ((CGFloat) -> Void)? {
|
||||
willSet {
|
||||
if animatingTimer == nil {
|
||||
animatingTimer = Timer(timeInterval: 0, target: self, selector: #selector(CAAnimationDelegateImpl.animationIsAnimating(_:)), userInfo: nil, repeats: true)
|
||||
}
|
||||
}
|
||||
/// start: A block (closure) object to be executed when the animation starts. This block has no return value and takes no argument.
|
||||
var start: (() -> Void)?
|
||||
|
||||
/// completion: A block (closure) object to be executed when the animation ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished.
|
||||
var completion: ((Bool) -> Void)?
|
||||
|
||||
/// startTime: animation start date
|
||||
fileprivate var startTime: Date!
|
||||
fileprivate var animationDuration: TimeInterval!
|
||||
fileprivate var animatingTimer: Timer!
|
||||
|
||||
/// animating: A block (closure) object to be executed when the animation is animating. This block has no return value and takes a single CGFloat argument that indicates the progress of the animation (From 0 ..< 1)
|
||||
var animating: ((CGFloat) -> Void)? {
|
||||
willSet {
|
||||
if animatingTimer == nil {
|
||||
animatingTimer = Timer(timeInterval: 0, target: self, selector: #selector(CAAnimationDelegateImpl.animationIsAnimating(_:)), userInfo: nil, repeats: true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the animation begins its active duration.
|
||||
|
||||
- parameter theAnimation: the animation about to start
|
||||
*/
|
||||
func animationDidStart(_ theAnimation: CAAnimation) {
|
||||
start?()
|
||||
if animating != nil {
|
||||
animationDuration = theAnimation.duration
|
||||
startTime = Date()
|
||||
RunLoop.current.add(animatingTimer, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the animation begins its active duration.
|
||||
|
||||
- parameter theAnimation: the animation about to start
|
||||
*/
|
||||
func animationDidStart(_ theAnimation: CAAnimation) {
|
||||
start?()
|
||||
if animating != nil {
|
||||
animationDuration = theAnimation.duration
|
||||
startTime = Date()
|
||||
RunLoop.current.add(animatingTimer, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the animation completes its active duration or is removed from the object it is attached to.
|
||||
|
||||
- parameter theAnimation: the animation about to end
|
||||
- parameter finished: A Boolean value indicates whether or not the animations actually finished.
|
||||
*/
|
||||
func animationDidStop(_ theAnimation: CAAnimation, finished: Bool) {
|
||||
completion?(finished)
|
||||
animatingTimer?.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the animation is executing
|
||||
|
||||
- parameter timer: timer
|
||||
*/
|
||||
func animationIsAnimating(_ timer: Timer) {
|
||||
let progress = CGFloat(Date().timeIntervalSince(startTime) / animationDuration)
|
||||
if progress <= 1.0 {
|
||||
animating?(progress)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the animation completes its active duration or is removed from the object it is attached to.
|
||||
|
||||
- parameter theAnimation: the animation about to end
|
||||
- parameter finished: A Boolean value indicates whether or not the animations actually finished.
|
||||
*/
|
||||
func animationDidStop(_ theAnimation: CAAnimation, finished: Bool) {
|
||||
completion?(finished)
|
||||
animatingTimer?.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the animation is executing
|
||||
|
||||
- parameter timer: timer
|
||||
*/
|
||||
func animationIsAnimating(_ timer: Timer) {
|
||||
let progress = CGFloat(Date().timeIntervalSince(startTime) / animationDuration)
|
||||
if progress <= 1.0 {
|
||||
animating?(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension CAAnimation {
|
||||
/// A block (closure) object to be executed when the animation starts. This block has no return value and takes no argument.
|
||||
public var start: (() -> Void)? {
|
||||
set {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
animationDelegate.start = newValue
|
||||
} else {
|
||||
let animationDelegate = CAAnimationDelegateImpl()
|
||||
animationDelegate.start = newValue
|
||||
delegate = animationDelegate
|
||||
}
|
||||
}
|
||||
|
||||
get {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
return animationDelegate.start
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
/// A block (closure) object to be executed when the animation starts. This block has no return value and takes no argument.
|
||||
public var start: (() -> Void)? {
|
||||
set {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
animationDelegate.start = newValue
|
||||
} else {
|
||||
let animationDelegate = CAAnimationDelegateImpl()
|
||||
animationDelegate.start = newValue
|
||||
delegate = animationDelegate
|
||||
}
|
||||
}
|
||||
|
||||
/// A block (closure) object to be executed when the animation ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished.
|
||||
public var completion: ((Bool) -> Void)? {
|
||||
set {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
animationDelegate.completion = newValue
|
||||
} else {
|
||||
let animationDelegate = CAAnimationDelegateImpl()
|
||||
animationDelegate.completion = newValue
|
||||
delegate = animationDelegate
|
||||
}
|
||||
}
|
||||
|
||||
get {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
return animationDelegate.completion
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
get {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
return animationDelegate.start
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// A block (closure) object to be executed when the animation ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished.
|
||||
public var completion: ((Bool) -> Void)? {
|
||||
set {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
animationDelegate.completion = newValue
|
||||
} else {
|
||||
let animationDelegate = CAAnimationDelegateImpl()
|
||||
animationDelegate.completion = newValue
|
||||
delegate = animationDelegate
|
||||
}
|
||||
}
|
||||
|
||||
/// A block (closure) object to be executed when the animation is animating. This block has no return value and takes a single CGFloat argument that indicates the progress of the animation (From 0 ..< 1)
|
||||
public var animating: ((CGFloat) -> Void)? {
|
||||
set {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
animationDelegate.animating = newValue
|
||||
} else {
|
||||
let animationDelegate = CAAnimationDelegateImpl()
|
||||
animationDelegate.animating = newValue
|
||||
delegate = animationDelegate
|
||||
}
|
||||
}
|
||||
|
||||
get {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
return animationDelegate.animating
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
get {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
return animationDelegate.completion
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Alias to `animating`
|
||||
public var progress: ((CGFloat) -> Void)? {
|
||||
set {
|
||||
animating = newValue
|
||||
}
|
||||
|
||||
get {
|
||||
return animating
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A block (closure) object to be executed when the animation is animating. This block has no return value and takes a single CGFloat argument that indicates the progress of the animation (From 0 ..< 1)
|
||||
public var animating: ((CGFloat) -> Void)? {
|
||||
set {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
animationDelegate.animating = newValue
|
||||
} else {
|
||||
let animationDelegate = CAAnimationDelegateImpl()
|
||||
animationDelegate.animating = newValue
|
||||
delegate = animationDelegate
|
||||
}
|
||||
}
|
||||
|
||||
get {
|
||||
if let animationDelegate = delegate as? CAAnimationDelegateImpl {
|
||||
return animationDelegate.animating
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias to `animating`
|
||||
public var progress: ((CGFloat) -> Void)? {
|
||||
set {
|
||||
animating = newValue
|
||||
}
|
||||
|
||||
get {
|
||||
return animating
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension CALayer {
|
||||
/**
|
||||
Add the specified animation object to the layer’s render tree. Could provide a completion closure.
|
||||
|
||||
- parameter anim: The animation to be added to the render tree. This object is copied by the render tree, not referenced. Therefore, subsequent modifications to the object are not propagated into the render tree.
|
||||
- parameter key: A string that identifies the animation. Only one animation per unique key is added to the layer. The special key kCATransition is automatically used for transition animations. You may specify nil for this parameter.
|
||||
- parameter completion: A block object to be executed when the animation ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. Default value is nil.
|
||||
*/
|
||||
func addAnimation(_ anim: CAAnimation, forKey key: String?, withCompletion completion: ((Bool) -> Void)?) {
|
||||
anim.completion = completion
|
||||
add(anim, forKey: key)
|
||||
}
|
||||
/**
|
||||
Add the specified animation object to the layer’s render tree. Could provide a completion closure.
|
||||
|
||||
- parameter anim: The animation to be added to the render tree. This object is copied by the render tree, not referenced. Therefore, subsequent modifications to the object are not propagated into the render tree.
|
||||
- parameter key: A string that identifies the animation. Only one animation per unique key is added to the layer. The special key kCATransition is automatically used for transition animations. You may specify nil for this parameter.
|
||||
- parameter completion: A block object to be executed when the animation ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. Default value is nil.
|
||||
*/
|
||||
func addAnimation(_ anim: CAAnimation, forKey key: String?, withCompletion completion: ((Bool) -> Void)?) {
|
||||
anim.completion = completion
|
||||
add(anim, forKey: key)
|
||||
}
|
||||
}
|
||||
|
11
Source/thirdparty/CGFloat+Double.swift
vendored
11
Source/thirdparty/CGFloat+Double.swift
vendored
@ -1,11 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
internal extension CGFloat {
|
||||
|
||||
var doubleValue: Double {
|
||||
return Double(self)
|
||||
}
|
||||
|
||||
|
||||
var doubleValue: Double {
|
||||
return Double(self)
|
||||
}
|
||||
}
|
||||
|
67
Source/thirdparty/NSTimer+Closure.swift
vendored
67
Source/thirdparty/NSTimer+Closure.swift
vendored
@ -1,38 +1,37 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Timer {
|
||||
/**
|
||||
Creates and schedules a one-time `NSTimer` instance.
|
||||
|
||||
- Parameters:
|
||||
- delay: The delay before execution.
|
||||
- handler: A closure to execute after `delay`.
|
||||
|
||||
- Returns: The newly-created `NSTimer` instance.
|
||||
*/
|
||||
class func schedule(delay: TimeInterval, handler: @escaping (CFRunLoopTimer?) -> Void) -> CFRunLoopTimer? {
|
||||
let fireDate = delay + CFAbsoluteTimeGetCurrent()
|
||||
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0, handler)
|
||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
|
||||
return timer
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and schedules a repeating `NSTimer` instance.
|
||||
|
||||
- Parameters:
|
||||
- repeatInterval: The interval (in seconds) between each execution of
|
||||
`handler`. Note that individual calls may be delayed; subsequent calls
|
||||
to `handler` will be based on the time the timer was created.
|
||||
- handler: A closure to execute at each `repeatInterval`.
|
||||
|
||||
- Returns: The newly-created `NSTimer` instance.
|
||||
*/
|
||||
class func schedule(repeatInterval interval: TimeInterval, handler: @escaping (CFRunLoopTimer?) -> Void) -> CFRunLoopTimer? {
|
||||
let fireDate = interval + CFAbsoluteTimeGetCurrent()
|
||||
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, interval, 0, 0, handler)
|
||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
|
||||
return timer
|
||||
}
|
||||
/**
|
||||
Creates and schedules a one-time `NSTimer` instance.
|
||||
|
||||
- Parameters:
|
||||
- delay: The delay before execution.
|
||||
- handler: A closure to execute after `delay`.
|
||||
|
||||
- Returns: The newly-created `NSTimer` instance.
|
||||
*/
|
||||
class func schedule(delay: TimeInterval, handler: @escaping (CFRunLoopTimer?) -> Void) -> CFRunLoopTimer? {
|
||||
let fireDate = delay + CFAbsoluteTimeGetCurrent()
|
||||
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0, handler)
|
||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
|
||||
return timer
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and schedules a repeating `NSTimer` instance.
|
||||
|
||||
- Parameters:
|
||||
- repeatInterval: The interval (in seconds) between each execution of
|
||||
`handler`. Note that individual calls may be delayed; subsequent calls
|
||||
to `handler` will be based on the time the timer was created.
|
||||
- handler: A closure to execute at each `repeatInterval`.
|
||||
|
||||
- Returns: The newly-created `NSTimer` instance.
|
||||
*/
|
||||
class func schedule(repeatInterval interval: TimeInterval, handler: @escaping (CFRunLoopTimer?) -> Void) -> CFRunLoopTimer? {
|
||||
let fireDate = interval + CFAbsoluteTimeGetCurrent()
|
||||
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, interval, 0, 0, handler)
|
||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
|
||||
return timer
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import Foundation
|
||||
public typealias MEvent = UIEvent
|
||||
public typealias MTouch = UITouch
|
||||
public typealias MImage = UIImage
|
||||
public typealias MScrollView = UIScrollView
|
||||
public typealias MGestureRecognizer = UIGestureRecognizer
|
||||
public typealias MGestureRecognizerState = UIGestureRecognizerState
|
||||
public typealias MGestureRecognizerDelegate = UIGestureRecognizerDelegate
|
||||
@ -107,20 +106,20 @@ import Foundation
|
||||
self.mTouchesCancelled(touches, withEvent: event)
|
||||
}
|
||||
|
||||
open func mTouchesBegan(_ touches: Set<MTouch>, withEvent event: MEvent?) {
|
||||
super.touchesBegan(touches, with: event!)
|
||||
open func mTouchesBegan(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
}
|
||||
|
||||
open func mTouchesMoved(_ touches: Set<MTouch>, withEvent event: MEvent?) {
|
||||
super.touchesMoved(touches, with: event!)
|
||||
open func mTouchesMoved(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
}
|
||||
|
||||
open func mTouchesEnded(_ touches: Set<MTouch>, withEvent event: MEvent?) {
|
||||
super.touchesEnded(touches, with: event!)
|
||||
open func mTouchesEnded(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
}
|
||||
|
||||
open func mTouchesCancelled(_ touches: Set<MTouch>?, withEvent event: MEvent?) {
|
||||
super.touchesCancelled(touches!, with: event!)
|
||||
open func mTouchesCancelled(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ import Foundation
|
||||
public typealias MEvent = NSEvent
|
||||
public typealias MTouch = NSTouch
|
||||
public typealias MImage = NSImage
|
||||
public typealias MScrollView = NSScrollView
|
||||
public typealias MGestureRecognizer = NSGestureRecognizer
|
||||
public typealias MGestureRecognizerState = NSGestureRecognizerState
|
||||
public typealias MGestureRecognizerDelegate = NSGestureRecognizerDelegate
|
||||
@ -27,6 +26,17 @@ import Foundation
|
||||
public typealias MRotationGestureRecognizer = NSRotationGestureRecognizer
|
||||
public typealias MScreen = NSScreen
|
||||
|
||||
extension MGestureRecognizer {
|
||||
var cancelsTouchesInView: Bool {
|
||||
get {
|
||||
return false
|
||||
} set {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension MTapGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return 1
|
||||
@ -109,39 +119,43 @@ import Foundation
|
||||
return self.layer
|
||||
}
|
||||
|
||||
func didMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
}
|
||||
|
||||
func setNeedsDisplay() {
|
||||
self.setNeedsDisplay(self.bounds)
|
||||
}
|
||||
|
||||
open override func touchesBegan(with event: NSEvent) {
|
||||
self.mTouchesBegan(event.touches(matching: .any, in: self), withEvent: event)
|
||||
self.mTouchesBegan(event.touches(matching: .any, in: self), with: event)
|
||||
}
|
||||
|
||||
open override func touchesEnded(with event: NSEvent) {
|
||||
self.mTouchesEnded(event.touches(matching: .any, in: self), withEvent: event)
|
||||
self.mTouchesEnded(event.touches(matching: .any, in: self), with: event)
|
||||
}
|
||||
|
||||
open override func touchesMoved(with event: NSEvent) {
|
||||
self.mTouchesMoved(event.touches(matching: .any, in: self), withEvent: event)
|
||||
self.mTouchesMoved(event.touches(matching: .any, in: self), with: event)
|
||||
}
|
||||
|
||||
open override func touchesCancelled(with event: NSEvent) {
|
||||
self.mTouchesCancelled(event.touches(matching: .any, in: self), withEvent: event)
|
||||
self.mTouchesCancelled(event.touches(matching: .any, in: self), with: event)
|
||||
}
|
||||
|
||||
open func mTouchesBegan(_ touches: Set<MTouch>, withEvent event: MEvent?) {
|
||||
open func mTouchesBegan(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesBegan(with: event!)
|
||||
}
|
||||
|
||||
open func mTouchesMoved(_ touches: Set<MTouch>, withEvent event: MEvent?) {
|
||||
open func mTouchesMoved(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesMoved(with: event!)
|
||||
}
|
||||
|
||||
open func mTouchesEnded(_ touches: Set<MTouch>, withEvent event: MEvent?) {
|
||||
open func mTouchesEnded(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesEnded(with: event!)
|
||||
}
|
||||
|
||||
open func mTouchesCancelled(_ touches: Set<MTouch>?, withEvent event: MEvent?) {
|
||||
open func mTouchesCancelled(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesCancelled(with: event!)
|
||||
}
|
||||
}
|
||||
|
@ -6,34 +6,36 @@
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
var imagesMap = [String: MImage]()
|
||||
public extension MImage {
|
||||
public func image( xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) -> Image {
|
||||
|
||||
var oldId: String?
|
||||
for key in imagesMap.keys {
|
||||
if self === imagesMap[key] {
|
||||
oldId = key
|
||||
}
|
||||
}
|
||||
|
||||
let id = oldId ?? UUID().uuidString
|
||||
imagesMap[id] = self
|
||||
|
||||
return Image(src: "memory://\(id)",
|
||||
xAlign: xAlign, yAlign: yAlign,
|
||||
aspectRatio: aspectRatio,
|
||||
w: w, h: h,
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag)
|
||||
public func image( xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) -> Image {
|
||||
|
||||
var oldId: String?
|
||||
for key in imagesMap.keys {
|
||||
if self === imagesMap[key] {
|
||||
oldId = key
|
||||
}
|
||||
}
|
||||
|
||||
let id = oldId ?? UUID().uuidString
|
||||
imagesMap[id] = self
|
||||
|
||||
return Image(src: "memory://\(id)",
|
||||
xAlign: xAlign, yAlign: yAlign,
|
||||
aspectRatio: aspectRatio,
|
||||
w: w, h: h,
|
||||
place: place,
|
||||
opaque: opaque,
|
||||
opacity: opacity,
|
||||
clip: clip,
|
||||
effect: effect,
|
||||
visible: visible,
|
||||
tag: tag)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
@ -6,88 +8,88 @@ let nodesMap = NodesMap()
|
||||
var parentsMap = [Node: Set<Node>]()
|
||||
|
||||
class NodesMap {
|
||||
let map = NSMapTable<Node, MacawView>(keyOptions: NSMapTableWeakMemory, valueOptions: NSMapTableWeakMemory)
|
||||
|
||||
// MARK: - Macaw View
|
||||
func add(_ node: Node, view: MacawView) {
|
||||
map.setObject(view, forKey: node)
|
||||
|
||||
if let group = node as? Group {
|
||||
group.contents.forEach { child in
|
||||
self.add(child, view: view)
|
||||
self.add(child, parent: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getView(_ node: Node) -> MacawView? {
|
||||
return map.object(forKey: node)
|
||||
}
|
||||
|
||||
func remove(_ node: Node) {
|
||||
map.removeObject(forKey: node)
|
||||
parentsMap.removeValue(forKey: node)
|
||||
}
|
||||
|
||||
// MARK: - Parents
|
||||
func add(_ node: Node, parent: Node) {
|
||||
if var nodesSet = parentsMap[node] {
|
||||
nodesSet.insert(parent)
|
||||
} else {
|
||||
parentsMap[node] = Set([parent])
|
||||
}
|
||||
|
||||
if let group = node as? Group {
|
||||
group.contents.forEach { child in
|
||||
self.add(child, parent: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parents(_ node: Node) -> [Node] {
|
||||
guard let nodesSet = parentsMap[node] else {
|
||||
return []
|
||||
}
|
||||
|
||||
return Array(nodesSet)
|
||||
}
|
||||
let map = NSMapTable<Node, MacawView>(keyOptions: NSMapTableWeakMemory, valueOptions: NSMapTableWeakMemory)
|
||||
|
||||
// MARK: - Macaw View
|
||||
func add(_ node: Node, view: MacawView) {
|
||||
map.setObject(view, forKey: node)
|
||||
|
||||
func replace(node: Node, to: Node) {
|
||||
let parents = parentsMap[node]
|
||||
let hostingView = map.object(forKey: node)
|
||||
|
||||
remove(node)
|
||||
|
||||
parents?.forEach { parent in
|
||||
guard let group = parent as? Group else {
|
||||
return
|
||||
}
|
||||
|
||||
var contents = group.contents
|
||||
var indexToInsert = 0
|
||||
if let index = contents.index(of: node) {
|
||||
contents.remove(at: index)
|
||||
indexToInsert = index
|
||||
}
|
||||
|
||||
contents.insert(to, at: indexToInsert)
|
||||
group.contents = contents
|
||||
|
||||
add(to, parent: parent)
|
||||
}
|
||||
|
||||
if let view = hostingView {
|
||||
add(to, view: view)
|
||||
}
|
||||
|
||||
|
||||
// Replacing node in hosting view if needed
|
||||
guard let hostingNode = hostingView?.node else {
|
||||
return
|
||||
}
|
||||
|
||||
if hostingNode == node {
|
||||
hostingView?.node = to
|
||||
}
|
||||
if let group = node as? Group {
|
||||
group.contents.forEach { child in
|
||||
self.add(child, view: view)
|
||||
self.add(child, parent: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getView(_ node: Node) -> MacawView? {
|
||||
return map.object(forKey: node)
|
||||
}
|
||||
|
||||
func remove(_ node: Node) {
|
||||
map.removeObject(forKey: node)
|
||||
parentsMap.removeValue(forKey: node)
|
||||
}
|
||||
|
||||
// MARK: - Parents
|
||||
func add(_ node: Node, parent: Node) {
|
||||
if var nodesSet = parentsMap[node] {
|
||||
nodesSet.insert(parent)
|
||||
} else {
|
||||
parentsMap[node] = Set([parent])
|
||||
}
|
||||
|
||||
if let group = node as? Group {
|
||||
group.contents.forEach { child in
|
||||
self.add(child, parent: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parents(_ node: Node) -> [Node] {
|
||||
guard let nodesSet = parentsMap[node] else {
|
||||
return []
|
||||
}
|
||||
|
||||
return Array(nodesSet)
|
||||
}
|
||||
|
||||
func replace(node: Node, to: Node) {
|
||||
let parents = parentsMap[node]
|
||||
let hostingView = map.object(forKey: node)
|
||||
|
||||
remove(node)
|
||||
|
||||
parents?.forEach { parent in
|
||||
guard let group = parent as? Group else {
|
||||
return
|
||||
}
|
||||
|
||||
var contents = group.contents
|
||||
var indexToInsert = 0
|
||||
if let index = contents.index(of: node) {
|
||||
contents.remove(at: index)
|
||||
indexToInsert = index
|
||||
}
|
||||
|
||||
contents.insert(to, at: indexToInsert)
|
||||
group.contents = contents
|
||||
|
||||
add(to, parent: parent)
|
||||
}
|
||||
|
||||
if let view = hostingView {
|
||||
add(to, view: view)
|
||||
}
|
||||
|
||||
|
||||
// Replacing node in hosting view if needed
|
||||
guard let hostingNode = hostingView?.node else {
|
||||
return
|
||||
}
|
||||
|
||||
if hostingNode == node {
|
||||
hostingView?.node = to
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user