1
1
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:
manindaniil 2017-08-10 20:01:11 +07:00
parent 10766c3403
commit 536bc08709
101 changed files with 7728 additions and 7697 deletions

View File

@ -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;
};

View File

@ -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

View File

@ -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)

View File

@ -8,7 +8,6 @@
import Foundation
open class AnimatableVariable<T>: Variable<T> {
weak internal var node: Node?
weak internal var node: Node?
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}
}
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)"
}
}

View File

@ -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
})
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
})
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -1,5 +1,5 @@
public enum Align {
case min
case mid
case max
case min
case mid
case max
}

View File

@ -1,5 +1,5 @@
public enum AspectRatio {
case none
case meet
case slice
case none
case meet
case slice
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,8 +1,8 @@
import Foundation
open class Effect {
public init() {
}
public init() {
}
}

View File

@ -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
}
}

View File

@ -9,5 +9,4 @@ open class GaussianBlur: Effect {
self.radius = radius
self.input = input
}
}

View File

@ -9,5 +9,4 @@ open class Gradient: Fill {
self.userSpace = userSpace
self.stops = stops
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -9,5 +9,4 @@ open class Stop {
self.offset = offset
self.color = color
}
}

View File

@ -15,5 +15,4 @@ open class Stroke {
self.join = join
self.dashes = dashes
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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]))
}
}

View File

@ -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)!
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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 layers 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 layers 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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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!)
}
}

View File

@ -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

View File

@ -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