mirror of
https://github.com/exyte/Macaw.git
synced 2024-11-11 01:42:35 +03:00
Merge pull request #207 from exyte/task/swiftlint
Integrate SwiftLint in project
This commit is contained in:
commit
ff7c3e2df7
115
.swiftlint.yml
Normal file
115
.swiftlint.yml
Normal file
@ -0,0 +1,115 @@
|
||||
included:
|
||||
- Source
|
||||
- Tests
|
||||
excluded:
|
||||
- Dependencies
|
||||
disabled_rules:
|
||||
- unused_optional_binding
|
||||
- file_header
|
||||
- no_extension_access_modifier
|
||||
- number_separator
|
||||
- object_literal
|
||||
- pattern_matching_keywords
|
||||
- private_over_fileprivate
|
||||
- strict_fileprivate
|
||||
- sorted_imports
|
||||
- identifier_name # Disabled due to a lot of one symbol identifiers
|
||||
# Temporary disabled
|
||||
- explicit_type_interface
|
||||
- line_length
|
||||
- file_length
|
||||
- type_body_length
|
||||
- function_body_length
|
||||
- cyclomatic_complexity
|
||||
- function_parameter_count
|
||||
- type_name
|
||||
- force_unwrapping
|
||||
- explicit_top_level_acl
|
||||
- legacy_constructor
|
||||
- todo
|
||||
- implicitly_unwrapped_optional
|
||||
- attributes
|
||||
- force_cast
|
||||
- extension_access_modifier
|
||||
- large_tuple
|
||||
- no_grouping_extension
|
||||
opt_in_rules:
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
- closing_brace
|
||||
- closure_end_indentation
|
||||
- closure_parameter_position
|
||||
- closure_spacing
|
||||
- colon
|
||||
- comma
|
||||
- compiler_protocol_init
|
||||
- conditional_returns_on_newline
|
||||
- control_statement
|
||||
- discarded_notification_center_observer
|
||||
- discouraged_direct_init
|
||||
- dynamic_inline
|
||||
- empty_count
|
||||
- empty_enum_arguments
|
||||
- empty_parameters
|
||||
- empty_parentheses_with_trailing_closure
|
||||
- explicit_enum_raw_value
|
||||
- explicit_init
|
||||
- fatal_error_message
|
||||
- first_where
|
||||
- for_where
|
||||
- force_try
|
||||
- generic_type_name
|
||||
- identifier_name
|
||||
- implicit_getter
|
||||
- implicit_return
|
||||
- is_disjoint
|
||||
- joined_default_parameter
|
||||
- leading_whitespace
|
||||
- legacy_cggeometry_functions
|
||||
- legacy_constant
|
||||
- legacy_nsgeometry_functions
|
||||
- let_var_whitespace
|
||||
- mark
|
||||
- multiline_parameters
|
||||
- multiple_closures_with_trailing_closure
|
||||
- nesting
|
||||
- nimble_operator
|
||||
- notification_center_detachment
|
||||
- opening_brace
|
||||
- operator_usage_whitespace
|
||||
- operator_whitespace
|
||||
- overridden_super_call
|
||||
- private_outlet
|
||||
- private_unit_test
|
||||
- prohibited_super_call
|
||||
- protocol_property_accessors_order
|
||||
- quick_discouraged_call
|
||||
- redundant_discardable_let
|
||||
- redundant_nil_coalescing
|
||||
- redundant_optional_initialization
|
||||
- redundant_string_enum_value
|
||||
- redundant_void_return
|
||||
- return_arrow_whitespace
|
||||
- shorthand_operator
|
||||
- single_test_class
|
||||
- statement_position
|
||||
- superfluous_disable_command
|
||||
- switch_case_on_newline
|
||||
- syntactic_sugar
|
||||
- trailing_closure
|
||||
- trailing_comma
|
||||
- trailing_newline
|
||||
- trailing_semicolon
|
||||
- trailing_whitespace
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unused_closure_parameter
|
||||
- unused_enumerated
|
||||
- valid_ibinspectable
|
||||
- vertical_parameter_alignment
|
||||
- vertical_whitespace
|
||||
- void_return
|
||||
- weak_delegate
|
||||
- xctfail_message
|
||||
force_cast: warning
|
||||
shorthand_operator: warning
|
||||
empty_count: warning
|
@ -911,6 +911,7 @@
|
||||
57614B731F83D15600875933 /* Headers */,
|
||||
57614B741F83D15600875933 /* Resources */,
|
||||
57614B751F83D15600875933 /* CopyFiles */,
|
||||
665742FC1F988D93006612C6 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -930,6 +931,7 @@
|
||||
57FCD2691D76EA4600CC0FB6 /* Headers */,
|
||||
57FCD26A1D76EA4600CC0FB6 /* Resources */,
|
||||
57D202181D78047000A90D4F /* CopyFiles */,
|
||||
669D360B1F8F9B4C0048EF8B /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -1040,6 +1042,35 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
665742FC1F988D93006612C6 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect --format\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
|
||||
};
|
||||
669D360B1F8F9B4C0048EF8B /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect --format\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
57614AFC1F83D15600875933 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
|
@ -9,5 +9,5 @@
|
||||
import Foundation
|
||||
|
||||
open class AnimatableVariable<T>: Variable<T> {
|
||||
weak internal var node: Node?
|
||||
weak internal var node: Node?
|
||||
}
|
||||
|
@ -1,53 +1,53 @@
|
||||
public enum AnimationState {
|
||||
case initial
|
||||
case running
|
||||
case paused
|
||||
case initial
|
||||
case running
|
||||
case paused
|
||||
}
|
||||
|
||||
public class Animation {
|
||||
|
||||
internal init() {
|
||||
}
|
||||
|
||||
public func play() {
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 (() -> Void)) -> Animation {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -9,125 +9,125 @@
|
||||
import Foundation
|
||||
|
||||
enum AnimationType {
|
||||
case unknown
|
||||
case unknown
|
||||
case contents
|
||||
case affineTransformation
|
||||
case opacity
|
||||
case sequence
|
||||
case combine
|
||||
case affineTransformation
|
||||
case opacity
|
||||
case sequence
|
||||
case combine
|
||||
case morphing
|
||||
case shape
|
||||
case empty
|
||||
case empty
|
||||
}
|
||||
|
||||
class BasicAnimation: Animation {
|
||||
|
||||
var nodeId: String?
|
||||
var type = AnimationType.unknown
|
||||
let ID: String
|
||||
var next: BasicAnimation?
|
||||
var removeFunc: (() -> ())?
|
||||
var nodeId: String?
|
||||
var type = AnimationType.unknown
|
||||
let ID: String
|
||||
var next: BasicAnimation?
|
||||
var removeFunc: (() -> Void)?
|
||||
var delayed = false
|
||||
var manualStop = false
|
||||
var paused = false
|
||||
var pausedProgress = 0.0
|
||||
var progress = 0.0
|
||||
var repeatCount = 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: (() -> ())?
|
||||
var delay = 0.0
|
||||
var autoreverses = false
|
||||
var onProgressUpdate: ((Double) -> Void)?
|
||||
var easing = Easing.ease
|
||||
var completion: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
ID = UUID().uuidString
|
||||
super.init()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
override open func cycle() -> Animation {
|
||||
self.cycled = true
|
||||
|
||||
override open func cycle(_ count: Double) -> Animation {
|
||||
self.repeatCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
override open func onComplete(_ f: @escaping (() -> ())) -> Animation {
|
||||
self.completion = f
|
||||
return self
|
||||
}
|
||||
override open func easing(_ easing: Easing) -> Animation {
|
||||
self.easing = easing
|
||||
return self
|
||||
}
|
||||
|
||||
override open func play() {
|
||||
override open func autoreversed() -> Animation {
|
||||
self.autoreverses = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
override open func cycle() -> Animation {
|
||||
self.cycled = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
override open func onComplete(_ f: @escaping (() -> Void)) -> Animation {
|
||||
self.completion = f
|
||||
return self
|
||||
}
|
||||
|
||||
override open func play() {
|
||||
|
||||
manualStop = false
|
||||
paused = false
|
||||
|
||||
animationProducer.addAnimation(self)
|
||||
}
|
||||
|
||||
override open func stop() {
|
||||
animationProducer.addAnimation(self)
|
||||
}
|
||||
|
||||
override open func stop() {
|
||||
manualStop = true
|
||||
paused = false
|
||||
|
||||
|
||||
if delay > 0.0 {
|
||||
animationProducer.removeDelayed(animation: self)
|
||||
}
|
||||
|
||||
removeFunc?()
|
||||
}
|
||||
|
||||
|
||||
removeFunc?()
|
||||
}
|
||||
|
||||
override open func pause() {
|
||||
paused = true
|
||||
manualStop = false
|
||||
|
||||
|
||||
if delay > 0.0 {
|
||||
animationProducer.removeDelayed(animation: self)
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
override open func reverse() -> Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
func getDuration() -> Double { return 0 }
|
||||
func getDuration() -> Double { return 0 }
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
@ -135,8 +135,8 @@ extension BasicAnimation: Hashable {
|
||||
public var hashValue: Int {
|
||||
return ID.hashValue
|
||||
}
|
||||
|
||||
public static func ==(lhs: BasicAnimation, rhs: BasicAnimation) -> Bool {
|
||||
|
||||
public static func == (lhs: BasicAnimation, rhs: BasicAnimation) -> Bool {
|
||||
return lhs.ID == rhs.ID
|
||||
}
|
||||
}
|
||||
@ -148,84 +148,83 @@ extension BasicAnimation {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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>
|
||||
var variable: AnimatableVariable<T>
|
||||
let initialValue: T
|
||||
let timeFactory: (() -> ((Double) -> T))
|
||||
let duration: Double
|
||||
let logicalFps: UInt
|
||||
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
|
||||
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.duration = animationDuration
|
||||
self.timeFactory = { return valueFunc }
|
||||
self.vFunc = .none
|
||||
self.logicalFps = fps
|
||||
self.vFunc = .none
|
||||
self.logicalFps = fps
|
||||
|
||||
super.init()
|
||||
super.init()
|
||||
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
super.play()
|
||||
}
|
||||
|
||||
open override func getDuration() -> Double {
|
||||
open override func getDuration() -> Double {
|
||||
var totalDuration = autoreverses ? duration * 2.0 : duration
|
||||
totalDuration = totalDuration * (1.0 - pausedProgress)
|
||||
|
||||
return totalDuration
|
||||
}
|
||||
|
||||
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
|
||||
@ -236,11 +235,11 @@ internal class AnimationImpl<T: Interpolable>: BasicAnimation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vFunc = { (t: Double) -> T in
|
||||
return self.timeFactory()(timeFunc(t))
|
||||
}
|
||||
|
||||
|
||||
return vFunc!
|
||||
}
|
||||
|
||||
@ -248,27 +247,29 @@ internal class AnimationImpl<T: Interpolable>: BasicAnimation {
|
||||
|
||||
// For sequence completion
|
||||
class EmptyAnimation: BasicAnimation {
|
||||
required init(completion: @escaping (() -> ())) {
|
||||
super.init()
|
||||
required init(completion: @escaping (() -> Void)) {
|
||||
super.init()
|
||||
|
||||
self.completion = completion
|
||||
self.type = .empty
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -10,313 +10,312 @@ let animationProducer = AnimationProducer()
|
||||
|
||||
class AnimationProducer {
|
||||
|
||||
|
||||
var storedAnimations = [Node: BasicAnimation]()
|
||||
var storedAnimations = [Node: BasicAnimation]()
|
||||
var delayedAnimations = [BasicAnimation: Timer]()
|
||||
var displayLink: MDisplayLinkProtocol?
|
||||
|
||||
|
||||
struct ContentAnimationDesc {
|
||||
let animation: ContentsAnimation
|
||||
let layer: CALayer
|
||||
weak var cache: AnimationCache?
|
||||
let startDate: Date
|
||||
let finishDate: Date
|
||||
let completion: (()->())?
|
||||
let completion: (() -> Void)?
|
||||
}
|
||||
|
||||
|
||||
var contentsAnimations = [ContentAnimationDesc]()
|
||||
|
||||
func addAnimation(_ animation: BasicAnimation, withoutDelay: Bool = false) {
|
||||
|
||||
// Delay - launching timer
|
||||
if animation.delay > 0.0 && !withoutDelay {
|
||||
func addAnimation(_ animation: BasicAnimation, withoutDelay: Bool = false) {
|
||||
|
||||
let timer = Timer.schedule(delay: animation.delay, handler: { [weak self] _ in
|
||||
self?.addAnimation(animation, withoutDelay: true)
|
||||
// Delay - launching timer
|
||||
if animation.delay > 0.0 && !withoutDelay {
|
||||
|
||||
let timer = Timer.schedule(delay: animation.delay) { [weak self] _ in
|
||||
self?.addAnimation(animation, withoutDelay: true)
|
||||
_ = self?.delayedAnimations.removeValue(forKey: animation)
|
||||
animation.delayed = false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
animation.delayed = true
|
||||
delayedAnimations[animation] = timer
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Empty - executing completion
|
||||
if animation.type == .empty {
|
||||
executeCompletion(animation)
|
||||
return
|
||||
}
|
||||
|
||||
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 nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else {
|
||||
return
|
||||
}
|
||||
guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let macawView = nodesMap.getView(node) else {
|
||||
storedAnimations[node] = animation
|
||||
return
|
||||
}
|
||||
|
||||
guard let macawView = nodesMap.getView(node) else {
|
||||
storedAnimations[node] = animation
|
||||
return
|
||||
}
|
||||
|
||||
guard let layer = macawView.mLayer else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let cache = macawView.animationCache else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let cache = macawView.animationCache else {
|
||||
return
|
||||
}
|
||||
// swiftlint:disable superfluous_disable_command switch_case_alignment
|
||||
switch animation.type {
|
||||
case .unknown:
|
||||
return
|
||||
case .affineTransformation:
|
||||
addTransformAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
|
||||
switch animation.type {
|
||||
case .unknown:
|
||||
return
|
||||
case .affineTransformation:
|
||||
addTransformAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
|
||||
case .opacity:
|
||||
addOpacityAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .sequence:
|
||||
addAnimationSequence(animation)
|
||||
case .combine:
|
||||
addCombineAnimation(animation)
|
||||
case .opacity:
|
||||
addOpacityAnimation(animation, sceneLayer: 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: {
|
||||
addContentsAnimation(animation, cache: cache) {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
}
|
||||
case .morphing:
|
||||
addMorphingAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
||||
addMorphingAnimation(animation, sceneLayer: layer, animationCache: cache) {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
}
|
||||
case .shape:
|
||||
addShapeAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
||||
addShapeAnimation(animation, sceneLayer: layer, animationCache: cache) {
|
||||
if let next = animation.next {
|
||||
self.addAnimation(next)
|
||||
}
|
||||
})
|
||||
case .empty:
|
||||
executeCompletion(animation)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
case .empty:
|
||||
executeCompletion(animation)
|
||||
}
|
||||
// swiftlint:enable superfluous_disable_command switch_case_alignment
|
||||
}
|
||||
|
||||
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]()
|
||||
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]
|
||||
}
|
||||
if sequence.repeatCount > 0.0001 {
|
||||
for _ in 0..<Int(sequence.repeatCount) {
|
||||
sequenceAnimations.append(contentsOf: cycleAnimations)
|
||||
}
|
||||
} else {
|
||||
sequenceAnimations.append(contentsOf: cycleAnimations)
|
||||
}
|
||||
|
||||
// Completion
|
||||
if let completion = sequence.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
// Connecting animations
|
||||
for i in 0..<(sequenceAnimations.count - 1) {
|
||||
let animation = sequenceAnimations[i]
|
||||
animation.next = sequenceAnimations[i + 1]
|
||||
}
|
||||
|
||||
if let next = sequence.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
// Completion
|
||||
if let completion = sequence.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
|
||||
sequenceAnimations.last?.next = completionAnimation
|
||||
} else {
|
||||
if let next = sequence.next {
|
||||
sequenceAnimations.last?.next = next
|
||||
}
|
||||
}
|
||||
if let next = sequence.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
// Launching
|
||||
if let firstAnimation = sequence.animations.first {
|
||||
self.addAnimation(firstAnimation)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
// Reversing
|
||||
if combine.autoreverses {
|
||||
combine.animations.forEach { animation in
|
||||
animation.autoreverses = true
|
||||
}
|
||||
}
|
||||
|
||||
// repeat count
|
||||
if combine.repeatCount > 0.00001 {
|
||||
var sequence = [Animation]()
|
||||
// repeat count
|
||||
if combine.repeatCount > 0.00001 {
|
||||
var sequence = [Animation]()
|
||||
|
||||
for _ in 0..<Int(combine.repeatCount) {
|
||||
sequence.append(combine)
|
||||
}
|
||||
for _ in 0..<Int(combine.repeatCount) {
|
||||
sequence.append(combine)
|
||||
}
|
||||
|
||||
combine.repeatCount = 0.0
|
||||
addAnimationSequence(sequence.sequence())
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
longestAnimation?.next = completionAnimation
|
||||
|
||||
} else {
|
||||
if let next = combine.next {
|
||||
longestAnimation?.next = next
|
||||
}
|
||||
} else {
|
||||
if let next = combine.next {
|
||||
longestAnimation?.next = next
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
combine.removeFunc = {
|
||||
combine.animations.forEach { animation in
|
||||
animation.removeFunc?()
|
||||
}
|
||||
}
|
||||
combine.removeFunc = {
|
||||
combine.animations.forEach { animation in
|
||||
animation.removeFunc?()
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
combine.animations.forEach { animation in
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
}
|
||||
// Launching
|
||||
combine.animations.forEach { animation in
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Empty Animation
|
||||
fileprivate func executeCompletion(_ emptyAnimation: BasicAnimation) {
|
||||
emptyAnimation.completion?()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
func addStoredAnimations(_ node: Node) {
|
||||
if let animation = storedAnimations[node] {
|
||||
addAnimation(animation)
|
||||
storedAnimations.removeValue(forKey: node)
|
||||
}
|
||||
|
||||
guard let group = node as? Group else {
|
||||
return
|
||||
}
|
||||
guard let group = node as? Group else {
|
||||
return
|
||||
}
|
||||
|
||||
group.contents.forEach { child in
|
||||
addStoredAnimations(child)
|
||||
}
|
||||
}
|
||||
|
||||
group.contents.forEach { child in
|
||||
addStoredAnimations(child)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Contents animation
|
||||
|
||||
func addContentsAnimation(_ animation: BasicAnimation, cache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
|
||||
func addContentsAnimation(_ animation: BasicAnimation, cache: AnimationCache?, completion: @escaping (() -> Void)) {
|
||||
guard let contentsAnimation = animation as? ContentsAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) 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() {
|
||||
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,
|
||||
@ -325,9 +324,9 @@ class AnimationProducer {
|
||||
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
|
||||
completion: completion
|
||||
)
|
||||
|
||||
|
||||
contentsAnimations.append(animationDesc)
|
||||
|
||||
|
||||
if displayLink == nil {
|
||||
displayLink = MDisplayLink()
|
||||
displayLink?.startUpdates { [weak self] in
|
||||
@ -337,43 +336,43 @@ class AnimationProducer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func updateContentAnimations() {
|
||||
if contentsAnimations.count == 0 {
|
||||
if contentsAnimations.isEmpty {
|
||||
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 nodeId = animation.nodeId, let group = Node.nodeBy(id: nodeId) 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?()
|
||||
@ -383,14 +382,14 @@ class AnimationProducer {
|
||||
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)
|
||||
|
@ -1,103 +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
|
||||
class func absolutePosition(_ node: Node) -> Transform {
|
||||
return AnimationUtils.absoluteTransform(node, pos: node.place)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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])
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
zIndex += 1
|
||||
|
||||
currentNode = parent!
|
||||
parent = nodesMap.parents(parent!).first
|
||||
|
||||
return transform
|
||||
}
|
||||
|
||||
if useCache {
|
||||
indexCache[node] = zIndex
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return zIndex
|
||||
}
|
||||
|
||||
class func animatedNodes(root: Node, animationCache: AnimationCache) -> [Node] {
|
||||
if animationCache.isAnimating(root) {
|
||||
return [root]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
guard let rootGroup = root as? Group else {
|
||||
return []
|
||||
|
||||
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 result = [Node]()
|
||||
rootGroup.contents.forEach { child in
|
||||
let childAnimatedNodes = animatedNodes(root: child, animationCache: animationCache)
|
||||
result.append(contentsOf: childAnimatedNodes)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@
|
||||
//
|
||||
|
||||
public enum Easing {
|
||||
case ease
|
||||
case linear
|
||||
case easeIn
|
||||
case easeOut
|
||||
case easeInOut
|
||||
case ease
|
||||
case linear
|
||||
case easeIn
|
||||
case easeOut
|
||||
case easeInOut
|
||||
}
|
||||
|
@ -1,42 +1,42 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public extension Rect {
|
||||
|
||||
convenience init(cgRect: CGRect) {
|
||||
self.init(
|
||||
x: Double(cgRect.origin.x),
|
||||
y: Double(cgRect.origin.y),
|
||||
w: Double(cgRect.size.width),
|
||||
h: Double(cgRect.size.height))
|
||||
}
|
||||
|
||||
func cgRect() -> CGRect {
|
||||
return CGRect(x: self.x, y: self.y, width: self.w, height: self.h)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) -> Rect {
|
||||
|
||||
// TODO: Rewrite using math
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
return Rect(cgRect: self.cgRect().applying(cgTransform))
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y) w:\(self.w) h:\(self.h)"
|
||||
}
|
||||
|
||||
convenience init(cgRect: CGRect) {
|
||||
self.init(
|
||||
x: Double(cgRect.origin.x),
|
||||
y: Double(cgRect.origin.y),
|
||||
w: Double(cgRect.size.width),
|
||||
h: Double(cgRect.size.height))
|
||||
}
|
||||
|
||||
func cgRect() -> CGRect {
|
||||
return CGRect(x: self.x, y: self.y, width: self.w, height: self.h)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) -> Rect {
|
||||
|
||||
// TODO: Rewrite using math
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
return Rect(cgRect: self.cgRect().applying(cgTransform))
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y) w:\(self.w) h:\(self.h)"
|
||||
}
|
||||
}
|
||||
|
||||
public extension Point {
|
||||
public func cgPoint() -> CGPoint {
|
||||
return CGPoint(x: self.x, y: self.y)
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y)"
|
||||
}
|
||||
public func cgPoint() -> CGPoint {
|
||||
return CGPoint(x: self.x, y: self.y)
|
||||
}
|
||||
|
||||
public func description() -> String {
|
||||
return "x: \(self.x) y:\(self.y)"
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,26 @@
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable trailing_closure
|
||||
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
|
||||
})
|
||||
}
|
||||
// swiftlint:enable trailing_closure
|
||||
|
@ -1,9 +1,9 @@
|
||||
public protocol ContentsInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Array: ContentsInterpolation {
|
||||
public func interpolate(_ endValue: Array, progress: Double) -> Array {
|
||||
return self
|
||||
}
|
||||
public func interpolate(_ endValue: Array, progress: Double) -> Array {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +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
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
public protocol FillInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Fill: FillInterpolation {
|
||||
|
@ -1,3 +1,3 @@
|
||||
public protocol Interpolable {
|
||||
func interpolate(_ endValue: Self, progress: Double) -> Self
|
||||
func interpolate(_ endValue: Self, progress: Double) -> Self
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
public protocol LocusInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Locus: LocusInterpolation {
|
||||
public func interpolate(_ endValue: Locus, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
public func interpolate(_ endValue: Locus, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@
|
||||
//
|
||||
|
||||
public protocol ShapeInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Shape: ShapeInterpolation {
|
||||
public func interpolate(_ endValue: Shape, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
public func interpolate(_ endValue: Shape, progress: Double) -> Self {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
public protocol StrokeInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Stroke: StrokeInterpolation {
|
||||
|
@ -1,15 +1,14 @@
|
||||
|
||||
public protocol TransformInterpolation: Interpolable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Transform: TransformInterpolation {
|
||||
public func interpolate(_ endValue: Transform, progress: Double) -> Transform {
|
||||
return Transform(m11: self.m11.interpolate(endValue.m11, progress: progress),
|
||||
m12: self.m12.interpolate(endValue.m12, progress: progress),
|
||||
m21: self.m21.interpolate(endValue.m21, progress: progress),
|
||||
m22: self.m22.interpolate(endValue.m22, progress: progress),
|
||||
dx: self.dx.interpolate(endValue.dx, progress: progress),
|
||||
dy: self.dy.interpolate(endValue.dy, progress: progress))
|
||||
}
|
||||
public func interpolate(_ endValue: Transform, progress: Double) -> Transform {
|
||||
return Transform(m11: self.m11.interpolate(endValue.m11, progress: progress),
|
||||
m12: self.m12.interpolate(endValue.m12, progress: progress),
|
||||
m21: self.m21.interpolate(endValue.m21, progress: progress),
|
||||
m22: self.m22.interpolate(endValue.m22, progress: progress),
|
||||
dx: self.dx.interpolate(endValue.dx, progress: progress),
|
||||
dy: self.dy.interpolate(endValue.dy, progress: progress))
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
|
||||
func solveEquation(a: Double, b: Double, c: Double) -> (s1: Double?, s2: Double?) {
|
||||
let epsilon: Double = 0.000000001
|
||||
if (abs(a) < epsilon) {
|
||||
if (abs(b) < epsilon) {
|
||||
if abs(a) < epsilon {
|
||||
if abs(b) < epsilon {
|
||||
return (.none, .none)
|
||||
}
|
||||
let s = -c / b
|
||||
if (0.0 < s && s < 1.0) {
|
||||
if 0.0 < s && s < 1.0 {
|
||||
return (s, .none)
|
||||
}
|
||||
return (.none, .none)
|
||||
}
|
||||
|
||||
|
||||
let b2ac = b * b - 4.0 * c * a
|
||||
if (b2ac < 0.0) {
|
||||
if b2ac < 0.0 {
|
||||
return (.none, .none)
|
||||
}
|
||||
let sqrtb2ac = b2ac.squareRoot()
|
||||
@ -24,7 +23,7 @@ func solveEquation(a: Double, b: Double, c: Double) -> (s1: Double?, s2: Double?
|
||||
}
|
||||
let s2 = (-b - sqrtb2ac) / (2.0 * a)
|
||||
var r2: Double? = .none
|
||||
if ((epsilon < s2) && (1-s2 > epsilon)) {
|
||||
if (epsilon < s2) && (1-s2 > epsilon) {
|
||||
r2 = s2
|
||||
}
|
||||
return (r1, r2)
|
||||
@ -35,17 +34,17 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
|
||||
let bx = 6 * p0.x - 12 * p1.x + 6 * p2.x
|
||||
let cx = 3 * p1.x - 3 * p0.x
|
||||
let sx = solveEquation(a: ax, b: bx, c: cx)
|
||||
|
||||
|
||||
let ay = -1.0 * p0.y + 3.0 * p1.y - 3.0 * p2.y + 1.0 * p3.y
|
||||
let by = 3.0 * p0.y - 6.0 * p1.y + 3.0 * p2.y
|
||||
let cy = 3 * p1.y - 3 * p0.y
|
||||
let sy = solveEquation(a: ay, b: by, c: cy)
|
||||
|
||||
let solutions = [sx.s1, sx.s2, sy.s1, sy.s2].flatMap { $0 }
|
||||
var minX:Double? = .none
|
||||
var minY:Double? = .none
|
||||
var maxX:Double? = .none
|
||||
var maxY:Double? = .none
|
||||
var minX: Double? = .none
|
||||
var minY: Double? = .none
|
||||
var maxX: Double? = .none
|
||||
var maxY: Double? = .none
|
||||
for s in solutions {
|
||||
let p = BezierFunc2D(s, p0: p0, p1: p1, p2: p2, p3: p3)
|
||||
if let mx = minX {
|
||||
@ -55,7 +54,7 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
|
||||
} else {
|
||||
minX = p.x
|
||||
}
|
||||
|
||||
|
||||
if let my = minY {
|
||||
if my > p.y {
|
||||
minY = p.y
|
||||
@ -63,7 +62,7 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
|
||||
} else {
|
||||
minY = p.y
|
||||
}
|
||||
|
||||
|
||||
if let mx = maxX {
|
||||
if mx < p.x {
|
||||
maxX = p.x
|
||||
@ -71,7 +70,7 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
|
||||
} else {
|
||||
maxY = p.x
|
||||
}
|
||||
|
||||
|
||||
if let my = maxY {
|
||||
if my < p.y {
|
||||
maxY = p.y
|
||||
@ -80,41 +79,41 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
|
||||
maxY = p.y
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let minX = minX, let maxX = maxX, let minY = minY, let maxY = maxY {
|
||||
return Rect(x: minX, y: minY, w: maxX - minX, h: maxY - minY)
|
||||
}
|
||||
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return Rect(x: minX, y: minY, w: maxX - minX, h: maxY - minY)
|
||||
}
|
||||
|
@ -1,119 +1,118 @@
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
guard let firstSegment = path.segments.first else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let segmentLastPoint = segmentInfo.1 {
|
||||
if segment.isAbsolute() {
|
||||
currentPoint = segmentLastPoint
|
||||
} else {
|
||||
currentPoint = segmentLastPoint.add(currentPoint)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let segmentBezierPoint = segmentInfo.2 {
|
||||
if segment.isAbsolute() {
|
||||
cubicBezierPoint = segmentBezierPoint
|
||||
} else {
|
||||
cubicBezierPoint = segmentBezierPoint.add(currentPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bounds
|
||||
|
||||
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, currentPoint: currentPoint), 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:
|
||||
if let p = currentPoint {
|
||||
if (data[0] > p.x) {
|
||||
return (Rect(x: p.x, y: p.y, w: data[0] - p.x, h: 0.0), Point(x: data[0], y: p.y), .none)
|
||||
} else {
|
||||
return (Rect(x: data[0], y: p.y, w: p.x - data[0], h: 0.0), Point(x: data[0], y: p.y), .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, currentPoint: currentPoint), 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:
|
||||
if let p = currentPoint {
|
||||
if data[0] > p.x {
|
||||
return (Rect(x: p.x, y: p.y, w: data[0] - p.x, h: 0.0), Point(x: data[0], y: p.y), .none)
|
||||
} else {
|
||||
return (Rect(x: data[0], y: p.y, w: p.x - data[0], h: 0.0), Point(x: data[0], y: p.y), .none)
|
||||
}
|
||||
}
|
||||
return (Rect(x: 0.0, y: 0.0, w: data[0], h: 0.0), Point(x: data[0], y: 0.0), .none)
|
||||
case .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:
|
||||
if let p = currentPoint {
|
||||
if data[0] > p.y {
|
||||
return (Rect(x: p.x, y: p.y, w: 0.0, h: data[0] - p.y), Point(x: p.x, y: data[0]), .none)
|
||||
} else {
|
||||
return (Rect(x: p.x, y: data[0], w: 0.0, h: p.y - data[0]), Point(x: p.x, y: data[0]), .none)
|
||||
}
|
||||
}
|
||||
return (Rect(x: 0.0, y: 0.0, w: 0.0, h: data[0]), Point(x: 0.0, y: data[0]), .none)
|
||||
case .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)
|
||||
}
|
||||
}
|
||||
return (Rect(x: 0.0, y: 0.0, w: data[0], h: 0.0), Point(x: data[0], y: 0.0), .none)
|
||||
case .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:
|
||||
if let p = currentPoint {
|
||||
if (data[0] > p.y) {
|
||||
return (Rect(x: p.x, y: p.y, w: 0.0, h: data[0] - p.y), Point(x: p.x, y: data[0]), .none)
|
||||
} else {
|
||||
return (Rect(x: p.x, y: data[0], w: 0.0, h: p.y - data[0]), Point(x: p.x, y: data[0]), .none)
|
||||
}
|
||||
}
|
||||
return (Rect(x: 0.0, y: 0.0, w: 0.0, h: data[0]), Point(x: 0.0, y: data[0]), .none)
|
||||
case .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], currentPoint: Point?) -> 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])
|
||||
|
||||
return boundsWithDerivative(p0: p0, p1: p1, p2: p2, p3: p3) ?? Rect(x: 0, y: 0, w: 0, h:0)
|
||||
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])
|
||||
|
||||
return boundsWithDerivative(p0: p0, p1: p1, p2: p2, p3: p3) ?? Rect(x: 0, y: 0, w: 0, h: 0)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return boundsWithDerivative(p0: p0, p1: p1, p2: p2, p3: p3) ?? Rect(x: 0, y: 0, w: 0, h:0)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return boundsWithDerivative(p0: p0, p1: p1, p2: p2, p3: p3) ?? Rect(x: 0, y: 0, w: 0, h: 0)
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ import Foundation
|
||||
typealias func2D = ((_ t: Double) -> (Point))
|
||||
|
||||
func BezierFunc2D(_ t: Double, p0: Point, p1: Point, p2: Point, p3: Point) -> Point {
|
||||
return Point(
|
||||
x: polynom3(t, p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x),
|
||||
y: polynom3(t, p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y))
|
||||
return Point(
|
||||
x: polynom3(t, p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x),
|
||||
y: polynom3(t, p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y))
|
||||
}
|
||||
|
||||
func polynom3(_ t: Double, p0: Double, p1: Double, p2: Double, p3: Double) -> Double {
|
||||
let t1 = 1.0 - t
|
||||
return pow(t1, 3.0) * p0 + 3.0 * t * pow(t1, 2.0) * p1 + 3.0 * t * t * t1 * p2 + pow(t, 3.0) * p3
|
||||
let t1 = 1.0 - t
|
||||
return pow(t1, 3.0) * p0 + 3.0 * t * pow(t1, 2.0) * p1 + 3.0 * t * t * t1 * p2 + pow(t, 3.0) * p3
|
||||
}
|
||||
|
@ -1,62 +1,61 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
internal class AnimationSequence: BasicAnimation {
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
let animations: [BasicAnimation]
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0) {
|
||||
self.animations = animations
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0) {
|
||||
self.animations = animations
|
||||
|
||||
super.init()
|
||||
super.init()
|
||||
|
||||
self.type = .sequence
|
||||
self.nodeId = animations.first?.nodeId
|
||||
self.delay = delay
|
||||
}
|
||||
self.type = .sequence
|
||||
self.nodeId = animations.first?.nodeId
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override func getDuration() -> Double {
|
||||
let originalDuration = animations.map { $0.getDuration() } .reduce(0) { $0 + $1 }
|
||||
|
||||
override func getDuration() -> Double {
|
||||
let originalDuration = animations.map({ $0.getDuration() }).reduce(0, { $0 + $1 })
|
||||
|
||||
if autoreverses {
|
||||
return originalDuration * 2.0
|
||||
}
|
||||
|
||||
return originalDuration
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
return originalDuration
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
super.stop()
|
||||
|
||||
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
active.stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open override func pause() {
|
||||
super.pause()
|
||||
|
||||
|
||||
guard let active = (animations.filter { $0.isActive() }).first else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
@ -64,31 +63,31 @@ internal class AnimationSequence: BasicAnimation {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return .initial
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
var reversedAnimations = [BasicAnimation]()
|
||||
animations.forEach { animation in
|
||||
reversedAnimations.append(animation.reverse() as! BasicAnimation)
|
||||
}
|
||||
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
|
||||
let reversedSequence = reversedAnimations.reversed().sequence(delay: self.delay) as! BasicAnimation
|
||||
reversedSequence.completion = completion
|
||||
reversedSequence.progress = progress
|
||||
|
||||
return reversedSequence
|
||||
}
|
||||
return reversedSequence
|
||||
}
|
||||
}
|
||||
|
||||
public extension Sequence where Iterator.Element: Animation {
|
||||
public func sequence(delay: Double = 0.0) -> 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)
|
||||
}
|
||||
var sequence = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
sequence.append(animation as! BasicAnimation)
|
||||
}
|
||||
return AnimationSequence(animations: sequence, delay: delay)
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,65 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
internal class CombineAnimation: BasicAnimation {
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
let animations: [BasicAnimation]
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0, node: Node? = .none) {
|
||||
self.animations = animations
|
||||
self.animations = animations
|
||||
|
||||
super.init()
|
||||
super.init()
|
||||
|
||||
self.type = .combine
|
||||
self.nodeId = nodeId ?? animations.first?.nodeId
|
||||
self.delay = delay
|
||||
}
|
||||
self.type = .combine
|
||||
self.nodeId = nodeId ?? animations.first?.nodeId
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override func getDuration() -> Double {
|
||||
if let maxElement = animations.map({ $0.getDuration() }).max() {
|
||||
return maxElement
|
||||
}
|
||||
override func getDuration() -> Double {
|
||||
if let maxElement = animations.map({ $0.getDuration() }).max() {
|
||||
return maxElement
|
||||
}
|
||||
|
||||
return 0.0
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
var reversedAnimations = [BasicAnimation]()
|
||||
animations.forEach { animation in
|
||||
reversedAnimations.append(animation.reverse() as! BasicAnimation)
|
||||
}
|
||||
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
|
||||
let combineReversed = reversedAnimations.combine(delay: self.delay) as! BasicAnimation
|
||||
combineReversed.completion = completion
|
||||
combineReversed.progress = progress
|
||||
|
||||
return combineReversed
|
||||
}
|
||||
|
||||
return combineReversed
|
||||
}
|
||||
|
||||
open override func play() {
|
||||
animations.forEach { animation in
|
||||
animation.paused = false
|
||||
animation.manualStop = false
|
||||
}
|
||||
|
||||
|
||||
super.play()
|
||||
}
|
||||
|
||||
open override func stop() {
|
||||
open override func stop() {
|
||||
super.stop()
|
||||
|
||||
animations.forEach { animation in
|
||||
animation.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 {
|
||||
@ -68,12 +67,12 @@ internal class CombineAnimation: BasicAnimation {
|
||||
if state == .running {
|
||||
return .running
|
||||
}
|
||||
|
||||
|
||||
if state != .initial {
|
||||
result = state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -81,10 +80,10 @@ internal class CombineAnimation: BasicAnimation {
|
||||
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)
|
||||
}
|
||||
var toCombine = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
toCombine.append(animation as! BasicAnimation)
|
||||
}
|
||||
return CombineAnimation(animations: toCombine, delay: delay, node: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
|
||||
internal class ContentsAnimation: AnimationImpl<[Node]> {
|
||||
|
||||
|
||||
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
|
||||
nodeId = animatedGroup.id
|
||||
|
||||
|
||||
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
|
||||
nodeId = animatedGroup.id
|
||||
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
let factory = { () -> (Double) -> [Node] in
|
||||
let original = self.timeFactory()
|
||||
@ -30,36 +28,36 @@ internal class ContentsAnimation: AnimationImpl<[Node]> {
|
||||
return original(1.0 - t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let node = Node.nodeBy(id: nodeId!)
|
||||
let reversedAnimation = ContentsAnimation(animatedGroup: node as! Group,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
|
||||
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)
|
||||
_ = 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)
|
||||
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,34 @@
|
||||
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) {
|
||||
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Locus in
|
||||
return finalValue
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Pause state not available for discreet animation
|
||||
override public func pause() {
|
||||
stop()
|
||||
@ -40,40 +39,39 @@ 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)
|
||||
_ = 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)
|
||||
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) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@ -84,43 +82,43 @@ public extension AnimatableVariable where T: ContentsInterpolation {
|
||||
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 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)
|
||||
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)
|
||||
|
||||
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 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]
|
||||
@ -128,48 +126,47 @@ public extension AnimatableVariable where T: ContentsInterpolation {
|
||||
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)
|
||||
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)
|
||||
|
||||
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)
|
||||
!($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
|
||||
let toNodes = to.filter {
|
||||
return !($0 is Group || $0 is Shape)
|
||||
!($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
|
||||
fromNodes.forEach { node in
|
||||
let animation = node.opacityVar.animation(to: 0.0, during:during, delay: delay)
|
||||
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)
|
||||
|
||||
let animation = node.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
|
||||
return animations.combine(node: fromNode)
|
||||
}
|
||||
|
||||
|
||||
public func animate(from: Group? = nil, to: [Node], during: Double = 1.0, delay: Double = 0.0) {
|
||||
animation(from: from, to: to, during: during, delay: delay).play()
|
||||
}
|
||||
|
@ -1,36 +1,35 @@
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
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)
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
let factory = { () -> (Double) -> Double in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Double in
|
||||
@ -39,43 +38,43 @@ internal class OpacityAnimation: AnimationImpl<Double> {
|
||||
}
|
||||
|
||||
let node = Node.nodeBy(id: nodeId!)
|
||||
let reversedAnimation = OpacityAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
let reversedAnimation = OpacityAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
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 extension AnimatableVariable where T: DoubleInterpolation {
|
||||
public func animate(_ desc: OpacityAnimationDescription) {
|
||||
_ = 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)
|
||||
}
|
||||
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 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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
class ShapeAnimation: AnimationImpl<Shape> {
|
||||
convenience init(animatedNode: Shape, finalValue: Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
|
||||
let nodeId = animatedNode.id
|
||||
let interpolationFunc = { (t: Double) -> Shape in
|
||||
if t == 0 {
|
||||
let initialNode = Node.nodeBy(id: nodeId) as! Shape
|
||||
|
||||
|
||||
return Shape(form: initialNode.form,
|
||||
fill: initialNode.fill,
|
||||
stroke: initialNode.stroke,
|
||||
@ -25,33 +25,33 @@ class ShapeAnimation: AnimationImpl<Shape> {
|
||||
visible: initialNode.visible,
|
||||
tag: initialNode.tag)
|
||||
}
|
||||
|
||||
|
||||
return finalValue
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Pause state not available for discreet animation
|
||||
override public func pause() {
|
||||
stop()
|
||||
@ -59,9 +59,9 @@ class ShapeAnimation: AnimationImpl<Shape> {
|
||||
}
|
||||
|
||||
public extension AnimatableVariable {
|
||||
public func animate<T:Stroke>(from: T? = nil, to: T, during: Double = 1.0, delay: Double = 0.0) {
|
||||
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 {
|
||||
@ -70,18 +70,18 @@ public extension AnimatableVariable {
|
||||
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)
|
||||
|
||||
_ = 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 {
|
||||
|
||||
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 {
|
||||
@ -90,20 +90,20 @@ public extension AnimatableVariable {
|
||||
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) {
|
||||
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 {
|
||||
@ -112,18 +112,18 @@ public extension AnimatableVariable {
|
||||
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)
|
||||
|
||||
_ = 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 {
|
||||
|
||||
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 {
|
||||
@ -132,12 +132,12 @@ public extension AnimatableVariable {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -2,36 +2,36 @@ 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) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
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)
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
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
|
||||
nodeId = animatedNode.id
|
||||
|
||||
if autostart {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
let original = self.timeFactory()
|
||||
@ -41,61 +41,60 @@ internal class TransformAnimation: AnimationImpl<Transform> {
|
||||
}
|
||||
|
||||
let node = Node.nodeBy(id: nodeId!)
|
||||
let reversedAnimation = TransformAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
let reversedAnimation = TransformAnimation(animatedNode: node!,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
|
||||
return reversedAnimation
|
||||
}
|
||||
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 animate(_ desc: TransformAnimationDescription) {
|
||||
_ = 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 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(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 {
|
||||
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(_ 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(
|
||||
let rotation = Transform(
|
||||
m11: acos, m12: -asin,
|
||||
m21: asin, m22: acos,
|
||||
dx: 0.0, dy: 0.0
|
||||
@ -105,15 +104,15 @@ public extension AnimatableVariable where T: TransformInterpolation {
|
||||
dx: x ?? bounds.w / 2.0,
|
||||
dy: y ?? bounds.h / 2.0
|
||||
)
|
||||
|
||||
|
||||
let t1 = GeomUtils.concat(t1: move, t2: rotation)
|
||||
let t2 = GeomUtils.concat(t1: t1, t2: move.invert()!)
|
||||
let result = GeomUtils.concat(t1: origin, t2: t2)
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
|
@ -1,196 +1,194 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
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 = 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
|
||||
|
||||
class CachedLayer {
|
||||
let layer: ShapeLayer
|
||||
let animation: Animation
|
||||
var linksCounter = 1
|
||||
|
||||
required init(layer: ShapeLayer, animation: Animation) {
|
||||
self.layer = layer
|
||||
self.animation = animation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private func calculateAnimationScale(animation: Animation) -> CGFloat {
|
||||
guard let defaultScale = MMainScreen()?.mScale else {
|
||||
return 1.0
|
||||
|
||||
let sceneLayer: CALayer
|
||||
var layerCache = [Node: CachedLayer]()
|
||||
|
||||
required init(sceneLayer: CALayer) {
|
||||
self.sceneLayer = sceneLayer
|
||||
}
|
||||
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return defaultScale
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cachedLayer.linksCounter += 1
|
||||
|
||||
return cachedLayer.layer
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
private func calculateAnimationScale(animation: Animation) -> CGFloat {
|
||||
guard let defaultScale = MMainScreen()?.mScale else {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
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 += step
|
||||
}
|
||||
|
||||
return defaultScale * CGFloat(sqrt(maxArea))
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
return defaultScale * CGFloat(sqrt(maxArea))
|
||||
}
|
||||
|
||||
func freeLayer(_ node: Node) {
|
||||
guard let cachedLayer = layerCache[node] else {
|
||||
return
|
||||
|
||||
func isAnimating(_ node: Node) -> Bool {
|
||||
|
||||
if let _ = layerCache[node] {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
cachedLayer.linksCounter -= 1
|
||||
|
||||
if cachedLayer.linksCounter != 0 {
|
||||
return
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
let layer = cachedLayer.layer
|
||||
layerCache.removeValue(forKey: node)
|
||||
sceneLayer.setNeedsDisplay()
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
|
||||
func isAnimating(_ node: Node) -> Bool {
|
||||
|
||||
if let _ = layerCache[node] {
|
||||
return true
|
||||
|
||||
func containsAnimation(_ node: Node) -> Bool {
|
||||
if isAnimating(node) {
|
||||
return true
|
||||
}
|
||||
|
||||
if let group = node as? Group {
|
||||
return isChildrenAnimating(group)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func animations() -> [Animation] {
|
||||
|
||||
return layerCache.map { $0.1.animation }
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func containsAnimation(_ node: Node) -> Bool {
|
||||
if isAnimating(node) {
|
||||
return true
|
||||
|
||||
func replace(original: Node, replacement: Node) {
|
||||
guard let layer = layerCache[original] else {
|
||||
return
|
||||
}
|
||||
|
||||
layerCache[replacement] = layer
|
||||
layerCache.removeValue(forKey: original)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
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 = () -> Void
|
||||
|
||||
var restoreClosures = [RestoreClosure]()
|
||||
|
||||
func addRestoreClosure(_ closure: @escaping RestoreClosure) {
|
||||
restoreClosures.append(closure)
|
||||
}
|
||||
|
||||
open class func restore() {
|
||||
DispatchQueue.main.async {
|
||||
animationRestorer.restoreClosures.forEach { restoreClosure in
|
||||
restoreClosure()
|
||||
}
|
||||
|
||||
animationRestorer.restoreClosures.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
|
||||
// TODO: Implement better hash
|
||||
|
||||
extension Node: Hashable {
|
||||
public var hashValue: Int {
|
||||
return Unmanaged.passUnretained(self).toOpaque().hashValue
|
||||
}
|
||||
public var hashValue: Int {
|
||||
return Unmanaged.passUnretained(self).toOpaque().hashValue
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: Node, rhs: Node) -> Bool {
|
||||
return lhs === rhs
|
||||
return lhs === rhs
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -9,47 +9,46 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
|
||||
func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
|
||||
guard let morphingAnimation = animation as? MorphingAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let nodeId = animation.nodeId, let shape = Node.nodeBy(id: nodeId) as? Shape else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
||||
nodesMap.replace(node: shape, to: mutatingShape)
|
||||
animationCache?.replace(original: shape, replacement: mutatingShape)
|
||||
animation.nodeId = mutatingShape.id
|
||||
|
||||
|
||||
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,
|
||||
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)
|
||||
@ -57,33 +56,33 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim
|
||||
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 {
|
||||
@ -91,16 +90,16 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim
|
||||
} else {
|
||||
layer.strokeColor = MColor.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)}
|
||||
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)
|
||||
@ -114,18 +113,18 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func pathAnimation(from:Locus, to: Locus, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -6,29 +6,29 @@ import Foundation
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let opacityAnimation = animation as? OpacityAnimation else {
|
||||
return
|
||||
}
|
||||
func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
|
||||
guard let opacityAnimation = animation as? OpacityAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else {
|
||||
return
|
||||
}
|
||||
guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
// 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.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||
|
||||
generatedAnimation.completion = { finished in
|
||||
generatedAnimation.completion = { finished in
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
if animation.paused {
|
||||
animation.pausedProgress = animation.pausedProgress + animation.progress
|
||||
animation.pausedProgress += animation.progress
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(animation.pausedProgress)
|
||||
} else if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
@ -46,22 +46,22 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnimation.progress = { progress in
|
||||
generatedAnimation.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(t)
|
||||
let t = Double(progress)
|
||||
node.opacityVar.value = opacityAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(t)
|
||||
}
|
||||
|
||||
if let layer = animationCache?.layerForNode(node, animation: animation) {
|
||||
layer.add(generatedAnimation, forKey: animation.ID)
|
||||
@ -73,33 +73,33 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima
|
||||
|
||||
func opacityAnimationByFunc(_ valueFunc: (Double) -> Double, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
|
||||
|
||||
var opacityValues = [Double]()
|
||||
var timeValues = [Double]()
|
||||
var opacityValues = [Double]()
|
||||
var timeValues = [Double]()
|
||||
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
|
||||
var dt = 0.0
|
||||
var dt = 0.0
|
||||
var tValue = Array(stride(from: 0.0, to: 1.0, by: step))
|
||||
tValue.append(1.0)
|
||||
for t in tValue {
|
||||
for t in tValue {
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
let value = valueFunc(offset + dt)
|
||||
opacityValues.append(value)
|
||||
timeValues.append(dt)
|
||||
}
|
||||
let value = valueFunc(offset + dt)
|
||||
opacityValues.append(value)
|
||||
timeValues.append(dt)
|
||||
}
|
||||
|
||||
let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
opacityAnimation.fillMode = kCAFillModeForwards
|
||||
opacityAnimation.isRemovedOnCompletion = false
|
||||
let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
opacityAnimation.fillMode = kCAFillModeForwards
|
||||
opacityAnimation.isRemovedOnCompletion = false
|
||||
|
||||
opacityAnimation.duration = duration
|
||||
opacityAnimation.values = opacityValues
|
||||
opacityAnimation.keyTimes = timeValues as [NSNumber]?
|
||||
opacityAnimation.duration = duration
|
||||
opacityAnimation.values = opacityValues
|
||||
opacityAnimation.keyTimes = timeValues as [NSNumber]?
|
||||
|
||||
return opacityAnimation
|
||||
return opacityAnimation
|
||||
}
|
||||
|
@ -14,15 +14,15 @@ import Foundation
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
|
||||
guard let shapeAnimation = animation as? ShapeAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let nodeId = animation.nodeId, let shape = Node.nodeBy(id: nodeId) as? Shape else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
||||
nodesMap.replace(node: shape, to: mutatingShape)
|
||||
animationCache?.replace(original: shape, replacement: mutatingShape)
|
||||
@ -30,71 +30,70 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati
|
||||
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 = generateShapeAnimation(
|
||||
from:fromShape,
|
||||
to:toShape,
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
@ -102,101 +101,100 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati
|
||||
} else {
|
||||
layer.strokeColor = MColor.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)}
|
||||
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 generateShapeAnimation(from:Shape, to: Shape, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
|
||||
|
||||
fileprivate func generateShapeAnimation(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]
|
||||
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -1,43 +1,43 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
import AppKit
|
||||
#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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,35 +6,35 @@ import Foundation
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> ())) {
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return
|
||||
}
|
||||
func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
|
||||
guard let transformAnimation = animation as? TransformAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else {
|
||||
return
|
||||
}
|
||||
guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Creating proper animation
|
||||
var generatedAnimation: CAAnimation?
|
||||
// Creating proper animation
|
||||
var generatedAnimation: CAAnimation?
|
||||
|
||||
generatedAnimation = transformAnimationByFunc(node,
|
||||
valueFunc: transformAnimation.getVFunc(),
|
||||
duration: animation.getDuration(),
|
||||
offset: animation.pausedProgress,
|
||||
fps: transformAnimation.logicalFps)
|
||||
generatedAnimation = transformAnimationByFunc(node,
|
||||
valueFunc: transformAnimation.getVFunc(),
|
||||
duration: animation.getDuration(),
|
||||
offset: animation.pausedProgress,
|
||||
fps: transformAnimation.logicalFps)
|
||||
|
||||
guard let generatedAnim = generatedAnimation else {
|
||||
return
|
||||
}
|
||||
guard let generatedAnim = generatedAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
generatedAnim.completion = { finished in
|
||||
|
||||
if animation.paused {
|
||||
animation.pausedProgress = animation.pausedProgress + animation.progress
|
||||
animation.pausedProgress += animation.progress
|
||||
node.placeVar.value = transformAnimation.getVFunc()(animation.pausedProgress)
|
||||
} else if animation.manualStop {
|
||||
animation.pausedProgress = 0.0
|
||||
@ -45,71 +45,71 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani
|
||||
animation.progress = 1.0
|
||||
node.placeVar.value = transformAnimation.getVFunc()(1.0)
|
||||
}
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
|
||||
animationCache?.freeLayer(node)
|
||||
|
||||
if !animation.cycled &&
|
||||
!animation.manualStop &&
|
||||
!animation.paused {
|
||||
animation.completion?()
|
||||
}
|
||||
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
if !finished {
|
||||
animationRestorer.addRestoreClosure(completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
completion()
|
||||
}
|
||||
|
||||
generatedAnim.progress = { progress in
|
||||
generatedAnim.progress = { progress in
|
||||
|
||||
let t = Double(progress)
|
||||
node.placeVar.value = transformAnimation.getVFunc()(t)
|
||||
let t = Double(progress)
|
||||
node.placeVar.value = transformAnimation.getVFunc()(t)
|
||||
|
||||
animation.progress = t
|
||||
animation.onProgressUpdate?(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)
|
||||
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]()
|
||||
var timeValues = [Double]()
|
||||
|
||||
let step = 1.0 / (duration * Double(fps))
|
||||
var dt = 0.0
|
||||
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 {
|
||||
for t in tValue {
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
dt = t
|
||||
if 1.0 - dt < step {
|
||||
dt = 1.0
|
||||
}
|
||||
|
||||
timeValues.append(dt)
|
||||
let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + 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
|
||||
@ -118,9 +118,9 @@ func transformAnimationByFunc(_ node: Node, valueFunc: (Double) -> Transform, du
|
||||
transformAnimation.fillMode = kCAFillModeForwards
|
||||
transformAnimation.isRemovedOnCompletion = false
|
||||
|
||||
return transformAnimation
|
||||
return transformAnimation
|
||||
}
|
||||
|
||||
func fixedAngle(_ angle: CGFloat) -> CGFloat {
|
||||
return angle > -0.0000000000000000000000001 ? angle : CGFloat(2.0 * Double.pi) + angle
|
||||
return angle > -0.0000000000000000000000001 ? angle : CGFloat(2.0 * Double.pi) + angle
|
||||
}
|
||||
|
@ -9,14 +9,14 @@
|
||||
import Foundation
|
||||
|
||||
open class Disposable {
|
||||
|
||||
let handler: (()->())
|
||||
|
||||
init (_ disposeHandler: @escaping (()->()) ) {
|
||||
handler = disposeHandler
|
||||
}
|
||||
|
||||
open func dispose() {
|
||||
handler()
|
||||
}
|
||||
|
||||
let handler: (() -> Void)
|
||||
|
||||
init (_ disposeHandler: @escaping (() -> Void) ) {
|
||||
handler = disposeHandler
|
||||
}
|
||||
|
||||
open func dispose() {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
@ -7,24 +7,24 @@
|
||||
//
|
||||
|
||||
open class GroupDisposable {
|
||||
|
||||
fileprivate var items: [Disposable] = []
|
||||
|
||||
open func dispose() {
|
||||
for disposable in items {
|
||||
disposable.dispose()
|
||||
|
||||
fileprivate var items: [Disposable] = []
|
||||
|
||||
open func dispose() {
|
||||
for disposable in items {
|
||||
disposable.dispose()
|
||||
}
|
||||
items = []
|
||||
}
|
||||
items = []
|
||||
}
|
||||
|
||||
open func add(_ item: Disposable) {
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
|
||||
open func add(_ item: Disposable) {
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Disposable {
|
||||
public func addTo(_ group: GroupDisposable) {
|
||||
group.add(self)
|
||||
}
|
||||
public func addTo(_ group: GroupDisposable) {
|
||||
group.add(self)
|
||||
}
|
||||
}
|
||||
|
@ -9,39 +9,39 @@
|
||||
import Foundation
|
||||
|
||||
class ChangeHandler<T>: Equatable {
|
||||
let handle: ((T)->())
|
||||
|
||||
init(_ f: @escaping ((T)->()) ) {
|
||||
handle = f
|
||||
}
|
||||
|
||||
static func ==(lhs: ChangeHandler<T>, rhs: ChangeHandler<T>) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
let handle: ((T) -> Void)
|
||||
|
||||
init(_ f: @escaping ((T) -> Void) ) {
|
||||
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) }
|
||||
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) -> Void)) -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(_ v: T) {
|
||||
value = v
|
||||
}
|
||||
|
||||
@discardableResult open func onChange(_ f: @escaping ((T)->())) -> Disposable {
|
||||
let handler = ChangeHandler<T>(f)
|
||||
handlers.append(handler)
|
||||
return Disposable({ [weak self] in
|
||||
guard let index = self?.handlers.index(of: handler) else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.handlers.remove(at: index)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,16 @@
|
||||
import Foundation
|
||||
|
||||
open class Event {
|
||||
|
||||
public weak var node: Node?
|
||||
|
||||
var consumed = false
|
||||
|
||||
init(node: Node) {
|
||||
self.node = node
|
||||
}
|
||||
|
||||
public func consume() {
|
||||
consumed = true
|
||||
}
|
||||
|
||||
public weak var node: Node?
|
||||
|
||||
var consumed = false
|
||||
|
||||
init(node: Node) {
|
||||
self.node = node
|
||||
}
|
||||
|
||||
public func consume() {
|
||||
consumed = true
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
open class PanEvent : Event {
|
||||
|
||||
open let dx: Double
|
||||
open let dy: Double
|
||||
open let count: Int
|
||||
|
||||
init(node: Node, dx: Double, dy: Double, count: Int) {
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
self.count = count
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
open 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 class PinchEvent: Event {
|
||||
|
||||
open let scale: Double
|
||||
|
||||
init(node: Node, scale: Double) {
|
||||
self.scale = scale
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
open class RotateEvent : Event {
|
||||
|
||||
open let angle: Double
|
||||
|
||||
init(node: Node, angle: Double) {
|
||||
self.angle = angle
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
open class RotateEvent: Event {
|
||||
|
||||
open let angle: Double
|
||||
|
||||
init(node: Node, angle: Double) {
|
||||
self.angle = angle
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
open class TapEvent : Event {
|
||||
|
||||
open let location: Point
|
||||
|
||||
init(node: Node, location: Point) {
|
||||
self.location = location
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
open class TapEvent: Event {
|
||||
|
||||
open let location: Point
|
||||
|
||||
init(node: Node, location: Point) {
|
||||
self.location = location
|
||||
super.init(node: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,19 +6,18 @@
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
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
|
||||
|
||||
super.init(node: node)
|
||||
}
|
||||
public class TouchEvent: Event {
|
||||
|
||||
public let points: [TouchPoint]
|
||||
|
||||
public init(node: Node, points: [TouchPoint]) {
|
||||
self.points = points
|
||||
|
||||
super.init(node: node)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ public extension MacawView {
|
||||
defer {
|
||||
backgroundColor = currentColor
|
||||
}
|
||||
|
||||
|
||||
var frame = CGRect(origin: CGPoint.zero, size: size)
|
||||
let ctx = CGContext(path as CFURL, mediaBox: &frame, .none)!
|
||||
|
||||
@ -26,10 +26,10 @@ public extension MacawView {
|
||||
x: size.width / bounds.width,
|
||||
y: -size.height / bounds.height
|
||||
)
|
||||
|
||||
|
||||
context.cgContext = ctx
|
||||
renderer?.render(force: false, opacity: node.opacity)
|
||||
|
||||
|
||||
ctx.endPDFPage()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
public enum Align {
|
||||
case min
|
||||
case mid
|
||||
case max
|
||||
case min
|
||||
case mid
|
||||
case max
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
public enum AspectRatio {
|
||||
case none
|
||||
case meet
|
||||
case slice
|
||||
case none
|
||||
case meet
|
||||
case slice
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
public enum Baseline {
|
||||
case top
|
||||
case alphabetic
|
||||
case bottom
|
||||
case mid
|
||||
case top
|
||||
case alphabetic
|
||||
case bottom
|
||||
case mid
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 )
|
||||
@ -21,56 +21,56 @@ open class Color: Fill, Equatable {
|
||||
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 {
|
||||
let x = ( ( t & 0xff ) << 24 )
|
||||
let y = ( ( r & 0xff ) << 16 )
|
||||
let z = ( ( g & 0xff ) << 8 )
|
||||
let q = b & 0xff
|
||||
return Color( val: ( ( ( x | y ) | z ) | q ) )
|
||||
let q = b & 0xff
|
||||
return Color( val: ( ( ( x | y ) | z ) | q ) )
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
|
||||
public static func == (lhs: Color, rhs: Color) -> Bool {
|
||||
return lhs.val == rhs.val
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
open class Drawable {
|
||||
|
||||
open let visible: Bool
|
||||
open let tag: [String]
|
||||
|
||||
public init(visible: Bool = true, tag: [String] = []) {
|
||||
self.visible = visible
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
open let visible: Bool
|
||||
open let tag: [String]
|
||||
|
||||
public init(visible: Bool = true, tag: [String] = []) {
|
||||
self.visible = visible
|
||||
self.tag = tag
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
open class DropShadow: Effect {
|
||||
|
||||
open let radius: Double
|
||||
open let offset: Point
|
||||
open let color: Color
|
||||
open let input: Effect?
|
||||
|
||||
public init(radius: Double = 0, offset: Point = Point.origin, color: Color = Color.black, input: Effect? = nil) {
|
||||
self.radius = radius
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
self.input = input
|
||||
}
|
||||
|
||||
open let radius: Double
|
||||
open let offset: Point
|
||||
open let color: Color
|
||||
open let input: Effect?
|
||||
|
||||
public init(radius: Double = 0, offset: Point = Point.origin, color: Color = Color.black, input: Effect? = nil) {
|
||||
self.radius = radius
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
self.input = input
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
open class Effect {
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import Foundation
|
||||
|
||||
open class Fill {
|
||||
|
||||
public init() {
|
||||
}
|
||||
public init() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
open class Font {
|
||||
|
||||
|
||||
open let name: String
|
||||
open let size: Int
|
||||
open let weight: String
|
||||
|
||||
|
||||
public init(name: String = "Serif", size: Int = 12, weight: String = "normal") {
|
||||
self.name = name
|
||||
self.size = size
|
||||
|
@ -2,11 +2,11 @@ import Foundation
|
||||
|
||||
open class GaussianBlur: Effect {
|
||||
|
||||
open let radius: Double
|
||||
open let input: Effect?
|
||||
open let radius: Double
|
||||
open let input: Effect?
|
||||
|
||||
public init(radius: Double = 0, input: Effect? = nil) {
|
||||
self.radius = radius
|
||||
self.input = input
|
||||
}
|
||||
public init(radius: Double = 0, input: Effect? = nil) {
|
||||
self.radius = radius
|
||||
self.input = input
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import Foundation
|
||||
|
||||
open class Gradient: Fill {
|
||||
|
||||
open let userSpace: Bool
|
||||
open let stops: [Stop]
|
||||
open let userSpace: Bool
|
||||
open let stops: [Stop]
|
||||
|
||||
public init(userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.userSpace = userSpace
|
||||
self.stops = stops
|
||||
}
|
||||
public init(userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.userSpace = userSpace
|
||||
self.stops = stops
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
public enum LineCap {
|
||||
case butt
|
||||
case round
|
||||
case square
|
||||
case butt
|
||||
case round
|
||||
case square
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
public enum LineJoin {
|
||||
case miter
|
||||
case round
|
||||
case bevel
|
||||
case miter
|
||||
case round
|
||||
case bevel
|
||||
}
|
||||
|
@ -1,49 +1,49 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class LinearGradient: Gradient {
|
||||
|
||||
open var x1: Double
|
||||
open var y1: Double
|
||||
open var x2: Double
|
||||
open var y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0, userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
super.init(
|
||||
userSpace: userSpace,
|
||||
stops: stops
|
||||
)
|
||||
}
|
||||
|
||||
public init(degree: Double = 0, from: Color, to: Color) {
|
||||
self.x1 = degree >= 135 && degree < 270 ? 1 : 0
|
||||
self.y1 = degree < 225 ? 0 : 1
|
||||
self.x2 = degree < 90 || degree >= 315 ? 1 : 0
|
||||
self.y2 = degree >= 45 && degree < 180 ? 1 : 0
|
||||
super.init(
|
||||
userSpace: false,
|
||||
stops: [Stop(offset: 0, color: from), Stop(offset: 1, color: to)]
|
||||
)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) {
|
||||
// TODO: - Check logic
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
|
||||
let point1 = CGPoint(x: x1, y: y1).applying(cgTransform)
|
||||
x1 = point1.x.doubleValue
|
||||
y1 = point1.y.doubleValue
|
||||
|
||||
let point2 = CGPoint(x: x2, y: y2).applying(cgTransform)
|
||||
x2 = point2.x.doubleValue
|
||||
y2 = point2.y.doubleValue
|
||||
}
|
||||
|
||||
open var x1: Double
|
||||
open var y1: Double
|
||||
open var x2: Double
|
||||
open var y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0, userSpace: Bool = false, stops: [Stop] = []) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
super.init(
|
||||
userSpace: userSpace,
|
||||
stops: stops
|
||||
)
|
||||
}
|
||||
|
||||
public init(degree: Double = 0, from: Color, to: Color) {
|
||||
self.x1 = degree >= 135 && degree < 270 ? 1 : 0
|
||||
self.y1 = degree < 225 ? 0 : 1
|
||||
self.x2 = degree < 90 || degree >= 315 ? 1 : 0
|
||||
self.y2 = degree >= 45 && degree < 180 ? 1 : 0
|
||||
super.init(
|
||||
userSpace: false,
|
||||
stops: [Stop(offset: 0, color: from), Stop(offset: 1, color: to)]
|
||||
)
|
||||
}
|
||||
|
||||
func applyTransform(_ transform: Transform) {
|
||||
// TODO: - Check logic
|
||||
|
||||
let cgTransform = RenderUtils.mapTransform(transform)
|
||||
|
||||
let point1 = CGPoint(x: x1, y: y1).applying(cgTransform)
|
||||
x1 = point1.x.doubleValue
|
||||
y1 = point1.y.doubleValue
|
||||
|
||||
let point2 = CGPoint(x: x2, y: y2).applying(cgTransform)
|
||||
x2 = point2.x.doubleValue
|
||||
y2 = point2.y.doubleValue
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,40 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import Foundation
|
||||
|
||||
open class Stop {
|
||||
|
||||
open let offset: Double
|
||||
open let color: Color
|
||||
open let offset: Double
|
||||
open let color: Color
|
||||
|
||||
public init(offset: Double = 0, color: Color) {
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
}
|
||||
public init(offset: Double = 0, color: Color) {
|
||||
self.offset = offset
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,17 @@ import Foundation
|
||||
|
||||
open class Stroke {
|
||||
|
||||
open let fill: Fill
|
||||
open let width: Double
|
||||
open let cap: LineCap
|
||||
open let join: LineJoin
|
||||
open let dashes: [Double]
|
||||
open let fill: Fill
|
||||
open let width: Double
|
||||
open let cap: LineCap
|
||||
open let join: LineJoin
|
||||
open let dashes: [Double]
|
||||
|
||||
public init(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) {
|
||||
self.fill = fill
|
||||
self.width = width
|
||||
self.cap = cap
|
||||
self.join = join
|
||||
self.dashes = dashes
|
||||
}
|
||||
public init(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) {
|
||||
self.fill = fill
|
||||
self.width = width
|
||||
self.cap = cap
|
||||
self.join = join
|
||||
self.dashes = dashes
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
open class Arc: Locus {
|
||||
|
||||
open let ellipse: Ellipse
|
||||
open let shift: Double
|
||||
open let extent: Double
|
||||
|
||||
public init(ellipse: Ellipse, shift: Double = 0, extent: Double = 0) {
|
||||
self.ellipse = ellipse
|
||||
self.shift = shift
|
||||
self.extent = extent
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: ellipse.cx - ellipse.rx,
|
||||
y: ellipse.cy - ellipse.ry,
|
||||
w: ellipse.rx * 2.0,
|
||||
h: ellipse.ry * 2.0)
|
||||
}
|
||||
|
||||
open let ellipse: Ellipse
|
||||
open let shift: Double
|
||||
open let extent: Double
|
||||
|
||||
public init(ellipse: Ellipse, shift: Double = 0, extent: Double = 0) {
|
||||
self.ellipse = ellipse
|
||||
self.shift = shift
|
||||
self.extent = extent
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: ellipse.cx - ellipse.rx,
|
||||
y: ellipse.cy - ellipse.ry,
|
||||
w: ellipse.rx * 2.0,
|
||||
h: ellipse.ry * 2.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
open class Circle: Locus {
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let r: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, r: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.r = r
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - r,
|
||||
y: cy - r,
|
||||
w: r * 2.0,
|
||||
h: r * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: Ellipse(cx: cx, cy: cy, rx: r, ry: r), shift: shift, extent: extent)
|
||||
}
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let r: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, r: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.r = r
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - r,
|
||||
y: cy - r,
|
||||
w: r * 2.0,
|
||||
h: r * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: Ellipse(cx: cx, cy: cy, rx: r, ry: r), shift: shift, extent: extent)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
import Foundation
|
||||
|
||||
open class Ellipse: Locus {
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, rx: Double = 0, ry: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - rx,
|
||||
y: cy - ry,
|
||||
w: rx * 2.0,
|
||||
h: ry * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: self, shift: shift, extent: extent)
|
||||
}
|
||||
|
||||
open let cx: Double
|
||||
open let cy: Double
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(cx: Double = 0, cy: Double = 0, rx: Double = 0, ry: Double = 0) {
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: cx - rx,
|
||||
y: cy - ry,
|
||||
w: rx * 2.0,
|
||||
h: ry * 2.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func arc(shift: Double, extent: Double) -> Arc {
|
||||
return Arc(ellipse: self, shift: shift, extent: extent)
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
open class GeomUtils {
|
||||
|
||||
|
||||
fileprivate class func rectToPath(_ rect: Rect) -> Path {
|
||||
return MoveTo(x: rect.x, y: rect.y).lineTo(x: rect.x, y: rect.y + rect.h).lineTo(x: rect.x + rect.w, y: rect.y + rect.h).lineTo(x: rect.x + rect.w, y: rect.y).close().build()
|
||||
}
|
||||
|
||||
|
||||
fileprivate class func circleToPath(_ circle: Circle) -> Path {
|
||||
return MoveTo(x: circle.cx, y: circle.cy).m(-circle.r, 0).a(circle.r, circle.r, 0.0, true, false, circle.r * 2.0, 0.0).a(circle.r, circle.r, 0.0, true, false, -(circle.r * 2.0), 0.0).build()
|
||||
}
|
||||
|
||||
|
||||
fileprivate class func arcToPath(_ arc: Arc) -> Path {
|
||||
let rx = arc.ellipse.rx
|
||||
let ry = arc.ellipse.ry
|
||||
@ -17,32 +17,32 @@ open class GeomUtils {
|
||||
let cy = arc.ellipse.cy
|
||||
|
||||
var delta = arc.extent
|
||||
if (arc.shift == 0.0 && abs(arc.extent - Double.pi * 2.0) < 0.00001) {
|
||||
if arc.shift == 0.0 && abs(arc.extent - Double.pi * 2.0) < 0.00001 {
|
||||
delta = Double.pi * 2.0 - 0.001
|
||||
}
|
||||
let theta1 = arc.shift
|
||||
|
||||
let theta2 = theta1 + delta
|
||||
|
||||
|
||||
let x1 = cx + rx * cos(theta1)
|
||||
let y1 = cy + ry * sin(theta1)
|
||||
|
||||
|
||||
let x2 = cx + rx * cos(theta2)
|
||||
let y2 = cy + ry * sin(theta2)
|
||||
|
||||
|
||||
let largeArcFlag = abs(delta) > .pi ? true : false
|
||||
let sweepFlag = delta > 0.0 ? true : false
|
||||
|
||||
|
||||
return PathBuilder(segment: PathSegment(type: .M, data: [x1, y1])).A(rx, ry, 0.0, largeArcFlag, sweepFlag, x2, y2).build()
|
||||
}
|
||||
|
||||
|
||||
fileprivate class func pointToPath(_ point: Point) -> Path {
|
||||
return MoveTo(x: point.x, y: point.y).lineTo(x: point.x, y: point.y).build()
|
||||
}
|
||||
|
||||
|
||||
fileprivate class func pointsToPath(_ points: [Double], close: Bool = false) -> Path {
|
||||
var pb = PathBuilder(segment: PathSegment(type: .M, data: [points[0], points[1]]))
|
||||
if (points.count > 2) {
|
||||
if points.count > 2 {
|
||||
let parts = stride(from: 2, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) }
|
||||
for part in parts {
|
||||
pb = pb.lineTo(x: part[0], y: part[1])
|
||||
@ -53,7 +53,7 @@ open class GeomUtils {
|
||||
}
|
||||
return pb.build()
|
||||
}
|
||||
|
||||
|
||||
open class func locusToPath(_ locus: Locus) -> Path {
|
||||
if let rect = locus as? Rect {
|
||||
return rectToPath(rect)
|
||||
@ -63,7 +63,7 @@ open class GeomUtils {
|
||||
return arcToPath(arc)
|
||||
} else if let point = locus as? Point {
|
||||
return MoveTo(x: point.x, y: point.y).lineTo(x: point.x, y: point.y).build()
|
||||
} else if let line = locus as? Line {
|
||||
} else if let line = locus as? Line {
|
||||
return MoveTo(x: line.x1, y: line.y1).lineTo(x: line.x2, y: line.y2).build()
|
||||
} else if let polygon = locus as? Polygon {
|
||||
return pointsToPath(polygon.points, close: true)
|
||||
@ -72,7 +72,7 @@ open class GeomUtils {
|
||||
}
|
||||
fatalError("Unsupported locus: \(locus)")
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@ -82,40 +82,40 @@ open class GeomUtils {
|
||||
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)
|
||||
return GeomUtils.anchorRotation(node: node, place: place, anchor: center, angle: angle)
|
||||
}
|
||||
|
||||
|
||||
open class func anchorRotation(node: Node, place: Transform, anchor: Point, angle: Double) -> Transform {
|
||||
let move = Transform.move(dx: anchor.x, dy: anchor.y)
|
||||
|
||||
|
||||
let asin = sin(angle); let acos = cos(angle)
|
||||
|
||||
let rotation = Transform(
|
||||
|
||||
let rotation = Transform(
|
||||
m11: acos, m12: -asin,
|
||||
m21: asin, m22: acos,
|
||||
dx: 0.0, dy: 0.0
|
||||
)
|
||||
|
||||
|
||||
let t1 = GeomUtils.concat(t1: move, t2: rotation)
|
||||
let t2 = GeomUtils.concat(t1: t1, t2: move.invert()!)
|
||||
let result = GeomUtils.concat(t1: place, t2: t2)
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
open class func centerScale(node: Node, sx: Double, sy: Double) -> Transform {
|
||||
let center = GeomUtils.center(node: node)
|
||||
return Transform.move(dx: center.x * (1.0 - sx), dy: center.y * (1.0 - sy)).scale(sx: sx, sy: sy)
|
||||
}
|
||||
|
||||
|
||||
open class func center(node: Node) -> Point {
|
||||
guard let bounds = node.bounds() else {
|
||||
return Point()
|
||||
}
|
||||
|
||||
|
||||
return Point(x: bounds.x + bounds.w / 2.0, y: bounds.y + bounds.h / 2.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
open class Insets {
|
||||
|
||||
open let top: Double
|
||||
open let right: Double
|
||||
open let bottom: Double
|
||||
open let left: Double
|
||||
|
||||
public init(top: Double = 0, right: Double = 0, bottom: Double = 0, left: Double = 0) {
|
||||
self.top = top
|
||||
self.right = right
|
||||
self.bottom = bottom
|
||||
self.left = left
|
||||
}
|
||||
|
||||
open let top: Double
|
||||
open let right: Double
|
||||
open let bottom: Double
|
||||
open let left: Double
|
||||
|
||||
public init(top: Double = 0, right: Double = 0, bottom: Double = 0, left: Double = 0) {
|
||||
self.top = top
|
||||
self.right = right
|
||||
self.bottom = bottom
|
||||
self.left = left
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
import Foundation
|
||||
|
||||
open class Line: Locus {
|
||||
|
||||
open let x1: Double
|
||||
open let y1: Double
|
||||
open let x2: Double
|
||||
open let y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: min(x1, x2),
|
||||
y: min(y1, y2),
|
||||
w: abs(x1 - x2),
|
||||
h: abs(y1 - y2))
|
||||
}
|
||||
|
||||
open let x1: Double
|
||||
open let y1: Double
|
||||
open let x2: Double
|
||||
open let y2: Double
|
||||
|
||||
public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: min(x1, x2),
|
||||
y: min(y1, y2),
|
||||
w: abs(x1 - x2),
|
||||
h: abs(y1 - y2))
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
open class Locus {
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func bounds() -> Rect {
|
||||
return Rect()
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(with: Stroke) -> Shape {
|
||||
return Shape(form: self, stroke: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func fill(with: Fill) -> Shape {
|
||||
return Shape(form: self, fill: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape {
|
||||
return Shape(form: self, stroke: Stroke(fill: fill, width: width, cap: cap, join: join, dashes: dashes))
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func bounds() -> Rect {
|
||||
return Rect()
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(with: Stroke) -> Shape {
|
||||
return Shape(form: self, stroke: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func fill(with: Fill) -> Shape {
|
||||
return Shape(form: self, fill: with)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func stroke(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape {
|
||||
return Shape(form: self, stroke: Stroke(fill: fill, width: width, cap: cap, join: join, dashes: dashes))
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
open class MoveTo: PathBuilder {
|
||||
|
||||
// GENERATED NOT
|
||||
public init(x: Double, y: Double) {
|
||||
super.init(segment: PathSegment(type: .M, data: [x, y]))
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public init(x: Double, y: Double) {
|
||||
super.init(segment: PathSegment(type: .M, data: [x, y]))
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
open class Path: Locus {
|
||||
|
||||
open let segments: [PathSegment]
|
||||
|
||||
public init(segments: [PathSegment] = []) {
|
||||
self.segments = segments
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return pathBounds(self)!
|
||||
}
|
||||
|
||||
open let segments: [PathSegment]
|
||||
|
||||
public init(segments: [PathSegment] = []) {
|
||||
self.segments = segments
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return pathBounds(self)!
|
||||
}
|
||||
}
|
||||
|
@ -1,163 +1,163 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 e(_ x: Double, _ y: Double, _ w: Double, _ h: Double, _ startAngle: Double, _ arcAngle: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .e, data: [x, y, w, h, startAngle, arcAngle]), rest: self)
|
||||
}
|
||||
open let segment: PathSegment
|
||||
open let rest: PathBuilder?
|
||||
|
||||
// GENERATED NOT
|
||||
open func E(_ x: Double, _ y: Double, _ w: Double, _ h: Double, _ startAngle: Double, _ arcAngle: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .E, data: [x, y, w, h, startAngle, arcAngle]), 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
|
||||
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 e(_ x: Double, _ y: Double, _ w: Double, _ h: Double, _ startAngle: Double, _ arcAngle: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .e, data: [x, y, w, h, startAngle, arcAngle]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func E(_ x: Double, _ y: Double, _ w: Double, _ h: Double, _ startAngle: Double, _ arcAngle: Double) -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .E, data: [x, y, w, h, startAngle, arcAngle]), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func Z() -> PathBuilder {
|
||||
return PathBuilder(segment: PathSegment(type: .z), rest: self)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func build() -> Path {
|
||||
var segments: [PathSegment] = []
|
||||
var builder: PathBuilder? = self
|
||||
while builder != nil {
|
||||
segments.append(builder!.segment)
|
||||
builder = builder!.rest
|
||||
}
|
||||
return Path(segments: segments.reversed())
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
fileprivate func boolsToNum(_ largeArc: Bool, sweep: Bool) -> Double {
|
||||
return (largeArc ? 1 : 0) + (sweep ? 1 : 0) * 2
|
||||
}
|
||||
return Path(segments: segments.reversed())
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
fileprivate func boolsToNum(_ largeArc: Bool, sweep: Bool) -> Double {
|
||||
return (largeArc ? 1 : 0) + (sweep ? 1 : 0) * 2;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +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, .E:
|
||||
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, .E:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
public enum PathSegmentType {
|
||||
case M
|
||||
case L
|
||||
case C
|
||||
case Q
|
||||
case A
|
||||
case z
|
||||
case H
|
||||
case V
|
||||
case S
|
||||
case T
|
||||
case m
|
||||
case l
|
||||
case c
|
||||
case q
|
||||
case a
|
||||
case h
|
||||
case v
|
||||
case s
|
||||
case t
|
||||
case E
|
||||
case e
|
||||
case M
|
||||
case L
|
||||
case C
|
||||
case Q
|
||||
case A
|
||||
case z
|
||||
case H
|
||||
case V
|
||||
case S
|
||||
case T
|
||||
case m
|
||||
case l
|
||||
case c
|
||||
case q
|
||||
case a
|
||||
case h
|
||||
case v
|
||||
case s
|
||||
case t
|
||||
case E
|
||||
case e
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
import Foundation
|
||||
|
||||
open class Point: Locus {
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
|
||||
open static let origin: Point = Point( x: 0, y: 0 )
|
||||
|
||||
public init(x: Double = 0, y: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: x,
|
||||
y: y,
|
||||
w: 0.0,
|
||||
h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func add(_ point: Point) -> Point {
|
||||
return Point(
|
||||
x: self.x + point.x,
|
||||
y: self.y + point.y)
|
||||
}
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
|
||||
open static let origin: Point = Point( x: 0, y: 0 )
|
||||
|
||||
public init(x: Double = 0, y: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: x,
|
||||
y: y,
|
||||
w: 0.0,
|
||||
h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func add(_ point: Point) -> Point {
|
||||
return Point(
|
||||
x: self.x + point.x,
|
||||
y: self.y + point.y)
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,48 @@
|
||||
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() }
|
||||
|
||||
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
|
||||
|
||||
open let points: [Double]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,48 @@
|
||||
import Foundation
|
||||
|
||||
open class Polyline: Locus {
|
||||
|
||||
open let points: [Double]
|
||||
|
||||
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
|
||||
|
||||
open let points: [Double]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY,
|
||||
w: maxX - minX,
|
||||
h: maxY - minY)
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +1,68 @@
|
||||
import Foundation
|
||||
|
||||
open class Rect: Locus {
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(x: Double = 0, y: Double = 0, w: Double = 0, h: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func round(rx: Double, ry: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: rx, ry: ry)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func round(r: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: r, ry: r)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func contains(locus: Locus) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
class func zero() -> Rect {
|
||||
return Rect(x: 0.0, y: 0.0, w: 0.0, h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func move(offset: Point) -> Rect {
|
||||
return Rect(
|
||||
x: self.x + offset.x,
|
||||
y: self.y + offset.y,
|
||||
w: self.w,
|
||||
h: self.h)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func union(rect: Rect) -> Rect {
|
||||
return Rect(
|
||||
x: min(self.x, rect.x),
|
||||
y: min(self.y, rect.y),
|
||||
w: max(self.x + self.w, rect.x + rect.w) - min(self.x, rect.x),
|
||||
h: max(self.y + self.h, rect.y + rect.h) - min(self.y, rect.y))
|
||||
}
|
||||
|
||||
|
||||
open let x: Double
|
||||
open let y: Double
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(x: Double = 0, y: Double = 0, w: Double = 0, h: Double = 0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return self
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func round(rx: Double, ry: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: rx, ry: ry)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
public func round(r: Double) -> RoundRect {
|
||||
return RoundRect(rect: self, rx: r, ry: r)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func contains(locus: Locus) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
class func zero() -> Rect {
|
||||
return Rect(x: 0.0, y: 0.0, w: 0.0, h: 0.0)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func move(offset: Point) -> Rect {
|
||||
return Rect(
|
||||
x: self.x + offset.x,
|
||||
y: self.y + offset.y,
|
||||
w: self.w,
|
||||
h: self.h)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
open func union(rect: Rect) -> Rect {
|
||||
return Rect(
|
||||
x: min(self.x, rect.x),
|
||||
y: min(self.y, rect.y),
|
||||
w: max(self.x + self.w, rect.x + rect.w) - min(self.x, rect.x),
|
||||
h: max(self.y + self.h, rect.y + rect.h) - min(self.y, rect.y))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Rect {
|
||||
public static func ==(lhs: Rect, rhs: Rect) -> Bool {
|
||||
return lhs.x == rhs.x
|
||||
&& lhs.y == rhs.y
|
||||
&& lhs.w == rhs.w
|
||||
&& lhs.h == rhs.h
|
||||
}
|
||||
public static func == (lhs: Rect, rhs: Rect) -> Bool {
|
||||
return lhs.x == rhs.x
|
||||
&& lhs.y == rhs.y
|
||||
&& lhs.w == rhs.w
|
||||
&& lhs.h == rhs.h
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
open class RoundRect: Locus {
|
||||
|
||||
open let rect: Rect
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(rect: Rect, rx: Double = 0, ry: Double = 0) {
|
||||
self.rect = rect
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return rect
|
||||
}
|
||||
|
||||
open let rect: Rect
|
||||
open let rx: Double
|
||||
open let ry: Double
|
||||
|
||||
public init(rect: Rect, rx: Double = 0, ry: Double = 0) {
|
||||
self.rect = rect
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return rect
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
open class Size {
|
||||
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(w: Double = 0, h: Double = 0) {
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
|
||||
open let w: Double
|
||||
open let h: Double
|
||||
|
||||
public init(w: Double = 0, h: Double = 0) {
|
||||
self.w = w
|
||||
self.h = h
|
||||
}
|
||||
}
|
||||
|
@ -1,88 +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
|
||||
|
||||
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)
|
||||
}
|
||||
return Transform(m11: m22 / det, m12: -m12 / det, m21: -m21 / det, m22: m11 / det,
|
||||
dx: (m21 * dy - m22 * dx) / det,
|
||||
dy: (m12 * dx - m11 * dy) / det)
|
||||
}
|
||||
}
|
||||
|
@ -1,151 +1,150 @@
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
override public func nodesBy(tag: String) -> [Node] {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -5,166 +5,164 @@ import Foundation
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
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] = []) {
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
mImage = image()
|
||||
|
||||
guard let mImage = mImage else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return Rect(x: 0.0, y: 0.0,
|
||||
w: Double(mImage.size.width),
|
||||
h: Double(mImage.size.height))
|
||||
|
||||
}
|
||||
|
||||
internal enum ImageRepresentationType {
|
||||
case JPEG
|
||||
case PNG
|
||||
}
|
||||
|
||||
internal func base64encoded(type:ImageRepresentationType) -> String? {
|
||||
if let image = self.image() {
|
||||
switch (type) {
|
||||
case .JPEG:
|
||||
if let data = MImageJPEGRepresentation(image) {
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
case .PNG:
|
||||
if let data = MImagePNGRepresentation(image) {
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
}
|
||||
open let srcVar: Variable<String>
|
||||
open var src: String {
|
||||
get { return srcVar.value }
|
||||
set(val) { srcVar.value = val }
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func image() -> MImage? {
|
||||
|
||||
// image already loaded
|
||||
if let _ = mImage {
|
||||
return mImage
|
||||
open let xAlignVar: Variable<Align>
|
||||
open var xAlign: Align {
|
||||
get { return xAlignVar.value }
|
||||
set(val) { xAlignVar.value = val }
|
||||
}
|
||||
|
||||
// In-memory image
|
||||
if src.contains("memory") {
|
||||
let id = src.replacingOccurrences(of: "memory://", with: "")
|
||||
return imagesMap[id]
|
||||
}
|
||||
|
||||
// Base64 image
|
||||
let decodableFormat = ["image/png", "image/jpg"]
|
||||
for format in decodableFormat {
|
||||
let prefix = "data:\(format);base64,"
|
||||
if src.hasPrefix(prefix) {
|
||||
let src = String(self.src.suffix(from: prefix.endIndex))
|
||||
guard let decodedData = Data(base64Encoded: src, options: .ignoreUnknownCharacters) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return MImage(data: decodedData)
|
||||
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] = []) {
|
||||
|
||||
var oldId: String?
|
||||
for key in imagesMap.keys where 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))
|
||||
}
|
||||
|
||||
mImage = image()
|
||||
|
||||
guard let mImage = mImage else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return Rect(x: 0.0, y: 0.0,
|
||||
w: Double(mImage.size.width),
|
||||
h: Double(mImage.size.height))
|
||||
|
||||
}
|
||||
|
||||
internal enum ImageRepresentationType {
|
||||
case JPEG
|
||||
case PNG
|
||||
}
|
||||
|
||||
internal func base64encoded(type: ImageRepresentationType) -> String? {
|
||||
if let image = self.image() {
|
||||
switch type {
|
||||
case .JPEG:
|
||||
if let data = MImageJPEGRepresentation(image) {
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
case .PNG:
|
||||
if let data = MImagePNGRepresentation(image) {
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
}
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func image() -> MImage? {
|
||||
|
||||
// image already loaded
|
||||
if let _ = mImage {
|
||||
return mImage
|
||||
}
|
||||
|
||||
// In-memory image
|
||||
if src.contains("memory") {
|
||||
let id = src.replacingOccurrences(of: "memory://", with: "")
|
||||
return imagesMap[id]
|
||||
}
|
||||
|
||||
// Base64 image
|
||||
let decodableFormat = ["image/png", "image/jpg"]
|
||||
for format in decodableFormat {
|
||||
let prefix = "data:\(format);base64,"
|
||||
if src.hasPrefix(prefix) {
|
||||
let src = String(self.src.suffix(from: prefix.endIndex))
|
||||
guard let decodedData = Data(base64Encoded: src, options: .ignoreUnknownCharacters) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return MImage(data: decodedData)
|
||||
}
|
||||
}
|
||||
|
||||
// General case
|
||||
#if os(iOS)
|
||||
return MImage(named: src)
|
||||
#elseif os(OSX)
|
||||
return MImage(named: NSImage.Name(rawValue: src))
|
||||
#endif
|
||||
}
|
||||
|
||||
// General case
|
||||
#if os(iOS)
|
||||
return MImage(named: src)
|
||||
#elseif os(OSX)
|
||||
return MImage(named: NSImage.Name(rawValue: src))
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -2,172 +2,171 @@ 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 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 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 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 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 }
|
||||
}
|
||||
|
||||
open let effectVar: Variable<Effect?>
|
||||
open var effect: Effect? {
|
||||
get { return effectVar.value }
|
||||
set(val) { effectVar.value = val }
|
||||
}
|
||||
|
||||
internal var id: String {
|
||||
didSet {
|
||||
Node.map.removeObject(forKey: id as NSString)
|
||||
Node.map.setObject(self, forKey: id as NSString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ID map
|
||||
private static let map = NSMapTable<NSString, Node>(keyOptions: NSMapTableStrongMemory, valueOptions: NSMapTableWeakMemory)
|
||||
|
||||
|
||||
open static func nodeBy(id: String) -> Node? {
|
||||
return Node.map.object(forKey: id as NSString)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Searching
|
||||
public func nodeBy(tag: String) -> Node? {
|
||||
if self.tag.contains(tag) {
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
|
||||
@discardableResult public func onTouchPressed (_ f: @escaping (TouchEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchPressedHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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 {
|
||||
|
||||
@discardableResult public func onTouchMoved (_ f: @escaping (TouchEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchMovedHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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 {
|
||||
|
||||
@discardableResult public func onTouchReleased(_ f: @escaping (TouchEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<TouchEvent>(f)
|
||||
touchReleasedHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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 {
|
||||
|
||||
@discardableResult public func onTap(_ f: @escaping (TapEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<TapEvent>(f)
|
||||
tapHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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 {
|
||||
@discardableResult public func onPan(_ f: @escaping (PanEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<PanEvent>(f)
|
||||
panHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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 {
|
||||
|
||||
@discardableResult public func onRotate(_ f: @escaping (RotateEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<RotateEvent>(f)
|
||||
rotateHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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 {
|
||||
|
||||
@discardableResult public func onPinch(_ f: @escaping (PinchEvent) -> Void) -> Disposable {
|
||||
let handler = ChangeHandler<PinchEvent>(f)
|
||||
pinchHandlers.append(handler)
|
||||
|
||||
return Disposable({ [weak self] in
|
||||
|
||||
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) }
|
||||
}
|
||||
@ -175,75 +174,72 @@ open class Node: Drawable {
|
||||
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
|
||||
return !touchPressedHandlers.isEmpty
|
||||
}
|
||||
|
||||
func shouldCheckForMoved() -> Bool {
|
||||
return !touchMovedHandlers.isEmpty
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForReleased() -> Bool {
|
||||
return touchReleasedHandlers.count > 0
|
||||
return !touchReleasedHandlers.isEmpty
|
||||
}
|
||||
|
||||
func shouldCheckForTap() -> Bool {
|
||||
return tapHandlers.count > 0
|
||||
return !tapHandlers.isEmpty
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForPan() -> Bool {
|
||||
return panHandlers.count > 0
|
||||
return !panHandlers.isEmpty
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForRotate() -> Bool {
|
||||
return rotateHandlers.count > 0
|
||||
return !rotateHandlers.isEmpty
|
||||
}
|
||||
|
||||
|
||||
func shouldCheckForPinch() -> Bool {
|
||||
return pinchHandlers.count > 0
|
||||
return !pinchHandlers.isEmpty
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
self.id = NSUUID().uuidString
|
||||
|
||||
super.init(
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
self.placeVar.node = self
|
||||
self.opacityVar.node = self
|
||||
|
||||
Node.map.setObject(self, forKey: self.id as NSString)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
internal func bounds() -> Rect? {
|
||||
return Rect()
|
||||
}
|
||||
super.init(
|
||||
visible: visible,
|
||||
tag: tag
|
||||
)
|
||||
self.placeVar.node = self
|
||||
self.opacityVar.node = self
|
||||
|
||||
Node.map.setObject(self, forKey: self.id as NSString)
|
||||
}
|
||||
|
||||
// GENERATED NOT
|
||||
internal func bounds() -> Rect? {
|
||||
return Rect()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,16 +7,16 @@
|
||||
//
|
||||
|
||||
class SceneUtils {
|
||||
static func shapeCopy(from: Shape) -> Shape {
|
||||
return Shape(form: from.form,
|
||||
fill: from.fill,
|
||||
stroke: from.stroke,
|
||||
place: from.place,
|
||||
opaque: from.opaque,
|
||||
opacity: from.opacity,
|
||||
clip: from.clip,
|
||||
effect: from.effect,
|
||||
visible: from.visible,
|
||||
tag: from.tag)
|
||||
}
|
||||
static func shapeCopy(from: Shape) -> Shape {
|
||||
return Shape(form: from.form,
|
||||
fill: from.fill,
|
||||
stroke: from.stroke,
|
||||
place: from.place,
|
||||
opaque: from.opaque,
|
||||
opacity: from.opacity,
|
||||
clip: from.clip,
|
||||
effect: from.effect,
|
||||
visible: from.visible,
|
||||
tag: from.tag)
|
||||
}
|
||||
}
|
||||
|
@ -2,56 +2,56 @@ 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
|
||||
)
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
return bounds
|
||||
}
|
||||
}
|
||||
|
@ -1,122 +1,121 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
import AppKit
|
||||
#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 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 strokeVar: Variable<Stroke?>
|
||||
open var stroke: Stroke? {
|
||||
get { return strokeVar.value }
|
||||
set(val) { strokeVar.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 strokeVar: Variable<Stroke?>
|
||||
open var stroke: Stroke? {
|
||||
get { return strokeVar.value }
|
||||
set(val) { strokeVar.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 }
|
||||
}
|
||||
|
||||
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, stroke: Stroke? = nil, 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.strokeVar = Variable<Stroke?>(stroke)
|
||||
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.mSystemFontSize)
|
||||
self.textVar = Variable<String>(text)
|
||||
self.fontVar = Variable<Font?>(font)
|
||||
self.fillVar = Variable<Fill>(fill)
|
||||
self.strokeVar = Variable<Stroke?>(stroke)
|
||||
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
|
||||
)
|
||||
}
|
||||
var stringAttributes: [NSAttributedStringKey: AnyObject] = [:]
|
||||
stringAttributes[NSAttributedStringKey.font] = font
|
||||
let size = (text as NSString).size(withAttributes: 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
|
||||
|
||||
// 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.mSystemFontSize)
|
||||
}
|
||||
var stringAttributes: [NSAttributedStringKey: AnyObject] = [:]
|
||||
stringAttributes[NSAttributedStringKey.font] = font
|
||||
let size = (text as NSString).size(withAttributes: stringAttributes)
|
||||
return Rect(
|
||||
x: calculateAlignmentOffset(font: font),
|
||||
y: calculateBaselineOffset(font: font),
|
||||
w: size.width.doubleValue,
|
||||
h: size.height.doubleValue
|
||||
)
|
||||
}
|
||||
return -baselineOffset
|
||||
}
|
||||
|
||||
fileprivate func calculateAlignmentOffset(font: MFont) -> Double {
|
||||
let textAttributes = [
|
||||
NSAttributedStringKey.font: font
|
||||
]
|
||||
let textSize = NSString(string: text).size(withAttributes: textAttributes)
|
||||
var alignmentOffset = 0.0
|
||||
switch align {
|
||||
case .mid:
|
||||
alignmentOffset = (textSize.width / 2).doubleValue
|
||||
case .max:
|
||||
alignmentOffset = textSize.width.doubleValue
|
||||
default:
|
||||
break
|
||||
|
||||
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
|
||||
}
|
||||
return -baselineOffset
|
||||
}
|
||||
return -alignmentOffset
|
||||
}
|
||||
|
||||
|
||||
fileprivate func calculateAlignmentOffset(font: MFont) -> Double {
|
||||
let textAttributes = [
|
||||
NSAttributedStringKey.font: font
|
||||
]
|
||||
let textSize = NSString(string: text).size(withAttributes: textAttributes)
|
||||
var alignmentOffset = 0.0
|
||||
switch align {
|
||||
case .mid:
|
||||
alignmentOffset = (textSize.width / 2).doubleValue
|
||||
case .max:
|
||||
alignmentOffset = textSize.width.doubleValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
return -alignmentOffset
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,80 +9,79 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
|
||||
public typealias MRectCorner = UIRectCorner
|
||||
public typealias MFont = UIFont
|
||||
public typealias MColor = UIColor
|
||||
public typealias MEvent = UIEvent
|
||||
public typealias MTouch = UITouch
|
||||
public typealias MImage = UIImage
|
||||
public typealias MBezierPath = UIBezierPath
|
||||
public typealias MGestureRecognizer = UIGestureRecognizer
|
||||
public typealias MGestureRecognizerState = UIGestureRecognizerState
|
||||
public typealias MGestureRecognizerDelegate = UIGestureRecognizerDelegate
|
||||
public typealias MTapGestureRecognizer = UITapGestureRecognizer
|
||||
public typealias MPanGestureRecognizer = UIPanGestureRecognizer
|
||||
public typealias MPinchGestureRecognizer = UIPinchGestureRecognizer
|
||||
public typealias MRotationGestureRecognizer = UIRotationGestureRecognizer
|
||||
public typealias MScreen = UIScreen
|
||||
public typealias MViewContentMode = UIViewContentMode
|
||||
|
||||
extension MTapGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return numberOfTouches
|
||||
import UIKit
|
||||
|
||||
public typealias MRectCorner = UIRectCorner
|
||||
public typealias MFont = UIFont
|
||||
public typealias MColor = UIColor
|
||||
public typealias MEvent = UIEvent
|
||||
public typealias MTouch = UITouch
|
||||
public typealias MImage = UIImage
|
||||
public typealias MBezierPath = UIBezierPath
|
||||
public typealias MGestureRecognizer = UIGestureRecognizer
|
||||
public typealias MGestureRecognizerState = UIGestureRecognizerState
|
||||
public typealias MGestureRecognizerDelegate = UIGestureRecognizerDelegate
|
||||
public typealias MTapGestureRecognizer = UITapGestureRecognizer
|
||||
public typealias MPanGestureRecognizer = UIPanGestureRecognizer
|
||||
public typealias MPinchGestureRecognizer = UIPinchGestureRecognizer
|
||||
public typealias MRotationGestureRecognizer = UIRotationGestureRecognizer
|
||||
public typealias MScreen = UIScreen
|
||||
public typealias MViewContentMode = UIViewContentMode
|
||||
|
||||
extension MTapGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return numberOfTouches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MPanGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return numberOfTouches
|
||||
|
||||
extension MPanGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return numberOfTouches
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint {
|
||||
return super.location(ofTouch: touch, in: inView)
|
||||
}
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint {
|
||||
return super.location(ofTouch: touch, in: inView)
|
||||
|
||||
extension MRotationGestureRecognizer {
|
||||
final var mRotation: CGFloat {
|
||||
get {
|
||||
return rotation
|
||||
}
|
||||
|
||||
set {
|
||||
rotation = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MRotationGestureRecognizer {
|
||||
final var mRotation: CGFloat {
|
||||
get {
|
||||
return rotation
|
||||
}
|
||||
|
||||
set {
|
||||
rotation = newValue
|
||||
}
|
||||
|
||||
extension MPinchGestureRecognizer {
|
||||
var mScale: CGFloat {
|
||||
get {
|
||||
return scale
|
||||
}
|
||||
|
||||
set {
|
||||
scale = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint {
|
||||
return super.location(ofTouch: touch, in: inView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MPinchGestureRecognizer {
|
||||
var mScale: CGFloat {
|
||||
get {
|
||||
return scale
|
||||
}
|
||||
|
||||
set {
|
||||
scale = newValue
|
||||
}
|
||||
|
||||
extension MFont {
|
||||
class var mSystemFontSize: CGFloat {
|
||||
return UIFont.systemFontSize
|
||||
}
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint {
|
||||
return super.location(ofTouch: touch, in: inView)
|
||||
|
||||
extension UIScreen {
|
||||
var mScale: CGFloat {
|
||||
return self.scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MFont {
|
||||
class var mSystemFontSize: CGFloat {
|
||||
return UIFont.systemFontSize
|
||||
}
|
||||
}
|
||||
|
||||
extension UIScreen {
|
||||
var mScale: CGFloat {
|
||||
return self.scale
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -9,42 +9,42 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
|
||||
func MGraphicsGetCurrentContext() -> CGContext? {
|
||||
return UIGraphicsGetCurrentContext()
|
||||
}
|
||||
|
||||
func MGraphicsGetImageFromCurrentImageContext() -> MImage! {
|
||||
return UIGraphicsGetImageFromCurrentImageContext()
|
||||
}
|
||||
|
||||
func MGraphicsPushContext(_ context: CGContext) {
|
||||
UIGraphicsPushContext(context)
|
||||
}
|
||||
|
||||
func MGraphicsPopContext() {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
func MGraphicsEndImageContext() {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
func MImagePNGRepresentation(_ image: MImage) -> Data? {
|
||||
return UIImagePNGRepresentation(image)
|
||||
}
|
||||
|
||||
func MImageJPEGRepresentation(_ image: MImage, _ quality: CGFloat = 0.8) -> Data? {
|
||||
return UIImageJPEGRepresentation(image, quality)
|
||||
}
|
||||
|
||||
func MMainScreen() -> MScreen? {
|
||||
return MScreen.main
|
||||
}
|
||||
|
||||
func MGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) {
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
|
||||
}
|
||||
|
||||
import UIKit
|
||||
|
||||
func MGraphicsGetCurrentContext() -> CGContext? {
|
||||
return UIGraphicsGetCurrentContext()
|
||||
}
|
||||
|
||||
func MGraphicsGetImageFromCurrentImageContext() -> MImage! {
|
||||
return UIGraphicsGetImageFromCurrentImageContext()
|
||||
}
|
||||
|
||||
func MGraphicsPushContext(_ context: CGContext) {
|
||||
UIGraphicsPushContext(context)
|
||||
}
|
||||
|
||||
func MGraphicsPopContext() {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
func MGraphicsEndImageContext() {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
func MImagePNGRepresentation(_ image: MImage) -> Data? {
|
||||
return UIImagePNGRepresentation(image)
|
||||
}
|
||||
|
||||
func MImageJPEGRepresentation(_ image: MImage, _ quality: CGFloat = 0.8) -> Data? {
|
||||
return UIImageJPEGRepresentation(image, quality)
|
||||
}
|
||||
|
||||
func MMainScreen() -> MScreen? {
|
||||
return MScreen.main
|
||||
}
|
||||
|
||||
func MGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) {
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -8,29 +8,29 @@
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
|
||||
|
||||
class MDisplayLink: MDisplayLinkProtocol {
|
||||
private var displayLink: CADisplayLink?
|
||||
private var onUpdate: (() -> Void)?
|
||||
|
||||
|
||||
// MARK: - Lifecycle
|
||||
deinit {
|
||||
displayLink?.invalidate()
|
||||
}
|
||||
|
||||
//MARK: - MDisplayLinkProtocol
|
||||
|
||||
// MARK: - MDisplayLinkProtocol
|
||||
func startUpdates(_ onUpdate: @escaping () -> Void) {
|
||||
self.onUpdate = onUpdate
|
||||
|
||||
|
||||
displayLink = CADisplayLink(target: self, selector: #selector(updateHandler))
|
||||
displayLink?.frameInterval = 1
|
||||
displayLink?.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
}
|
||||
|
||||
|
||||
func invalidate() {
|
||||
displayLink?.invalidate()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
@objc func updateHandler() {
|
||||
onUpdate?()
|
||||
|
@ -10,88 +10,87 @@ import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
|
||||
|
||||
open class MView: UIView, Touchable {
|
||||
var mLayer: CALayer? {
|
||||
return self.layer
|
||||
}
|
||||
|
||||
|
||||
var mGestureRecognizers: [MGestureRecognizer]? {
|
||||
return self.gestureRecognizers
|
||||
}
|
||||
|
||||
|
||||
func removeGestureRecognizers() {
|
||||
self.gestureRecognizers?.removeAll()
|
||||
}
|
||||
|
||||
|
||||
open override func touchesBegan(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
|
||||
let touchPoints = touches.map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
|
||||
mTouchesBegan(touchPoints)
|
||||
}
|
||||
|
||||
|
||||
open override func touchesMoved(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
|
||||
let touchPoints = touches.map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
|
||||
self.mTouchesMoved(touchPoints)
|
||||
}
|
||||
|
||||
|
||||
open override func touchesEnded(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
|
||||
let touchPoints = touches.map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
|
||||
mTouchesEnded(touchPoints)
|
||||
}
|
||||
|
||||
|
||||
override open func touchesCancelled(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
|
||||
let touchPoints = touches.map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
|
||||
mTouchesCancelled(touchPoints)
|
||||
}
|
||||
|
||||
|
||||
func mTouchesBegan(_ touches: [MTouchEvent]) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
func mTouchesMoved(_ touches: [MTouchEvent]) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
func mTouchesEnded(_ touches: [MTouchEvent]) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
func mTouchesCancelled(_ touches: [MTouchEvent]) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -9,122 +9,119 @@
|
||||
import Foundation
|
||||
|
||||
#if os(OSX)
|
||||
import Cocoa
|
||||
import Quartz
|
||||
|
||||
public typealias MFont = NSFont
|
||||
public typealias MColor = NSColor
|
||||
public typealias MEvent = NSEvent
|
||||
public typealias MTouch = NSTouch
|
||||
public typealias MImage = NSImage
|
||||
public typealias MBezierPath = NSBezierPath
|
||||
public typealias MGestureRecognizer = NSGestureRecognizer
|
||||
import Cocoa
|
||||
import Quartz
|
||||
|
||||
public typealias MFont = NSFont
|
||||
public typealias MColor = NSColor
|
||||
public typealias MEvent = NSEvent
|
||||
public typealias MTouch = NSTouch
|
||||
public typealias MImage = NSImage
|
||||
public typealias MBezierPath = NSBezierPath
|
||||
public typealias MGestureRecognizer = NSGestureRecognizer
|
||||
public typealias MGestureRecognizerState = NSGestureRecognizer.State
|
||||
public typealias MGestureRecognizerDelegate = NSGestureRecognizerDelegate
|
||||
public typealias MTapGestureRecognizer = NSClickGestureRecognizer
|
||||
public typealias MPanGestureRecognizer = NSPanGestureRecognizer
|
||||
public typealias MPinchGestureRecognizer = NSMagnificationGestureRecognizer
|
||||
public typealias MRotationGestureRecognizer = NSRotationGestureRecognizer
|
||||
public typealias MScreen = NSScreen
|
||||
|
||||
extension MGestureRecognizer {
|
||||
var cancelsTouchesInView: Bool {
|
||||
get {
|
||||
return false
|
||||
} set { }
|
||||
public typealias MGestureRecognizerDelegate = NSGestureRecognizerDelegate
|
||||
public typealias MTapGestureRecognizer = NSClickGestureRecognizer
|
||||
public typealias MPanGestureRecognizer = NSPanGestureRecognizer
|
||||
public typealias MPinchGestureRecognizer = NSMagnificationGestureRecognizer
|
||||
public typealias MRotationGestureRecognizer = NSRotationGestureRecognizer
|
||||
public typealias MScreen = NSScreen
|
||||
|
||||
extension MGestureRecognizer {
|
||||
var cancelsTouchesInView: Bool {
|
||||
get {
|
||||
return false
|
||||
} set { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MTapGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return 1
|
||||
|
||||
extension MTapGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MPanGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return 1
|
||||
|
||||
extension MPanGestureRecognizer {
|
||||
func mNumberOfTouches() -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView: NSView?) -> NSPoint {
|
||||
return super.location(in: inView)
|
||||
}
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView: NSView?) -> NSPoint {
|
||||
return super.location(in: inView)
|
||||
|
||||
extension MRotationGestureRecognizer {
|
||||
var velocity: CGFloat {
|
||||
return 0.1
|
||||
}
|
||||
|
||||
var mRotation: CGFloat {
|
||||
get {
|
||||
return -rotation
|
||||
}
|
||||
|
||||
set {
|
||||
rotation = -newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MRotationGestureRecognizer {
|
||||
var velocity: CGFloat {
|
||||
return 0.1
|
||||
|
||||
extension MPinchGestureRecognizer {
|
||||
var mScale: CGFloat {
|
||||
get {
|
||||
return magnification + 1.0
|
||||
}
|
||||
|
||||
set {
|
||||
magnification = newValue - 1.0
|
||||
}
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView view: NSView?) -> NSPoint {
|
||||
return super.location(in: view)
|
||||
}
|
||||
}
|
||||
|
||||
var mRotation: CGFloat {
|
||||
get {
|
||||
return -rotation
|
||||
}
|
||||
|
||||
set {
|
||||
rotation = -newValue
|
||||
}
|
||||
|
||||
extension NSFont {
|
||||
var lineHeight: CGFloat {
|
||||
return self.boundingRectForFont.size.height
|
||||
}
|
||||
|
||||
class var mSystemFontSize: CGFloat {
|
||||
return NSFont.systemFontSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MPinchGestureRecognizer {
|
||||
var mScale: CGFloat {
|
||||
get {
|
||||
return magnification + 1.0
|
||||
}
|
||||
|
||||
set {
|
||||
magnification = newValue - 1.0
|
||||
}
|
||||
|
||||
extension NSScreen {
|
||||
var mScale: CGFloat {
|
||||
return self.backingScaleFactor
|
||||
}
|
||||
}
|
||||
|
||||
func mLocationOfTouch(_ touch: Int, inView view: NSView?) -> NSPoint {
|
||||
return super.location(in: view)
|
||||
|
||||
extension NSImage {
|
||||
var cgImage: CGImage? {
|
||||
return self.cgImage(forProposedRect: nil, context: nil, hints: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSFont {
|
||||
var lineHeight: CGFloat {
|
||||
return self.boundingRectForFont.size.height
|
||||
|
||||
extension NSTouch {
|
||||
func location(in view: NSView) -> NSPoint {
|
||||
let n = self.normalizedPosition
|
||||
let b = view.bounds
|
||||
return NSPoint(x: b.origin.x + b.size.width * n.x, y: b.origin.y + b.size.height * n.y)
|
||||
}
|
||||
}
|
||||
|
||||
class var mSystemFontSize: CGFloat {
|
||||
return NSFont.systemFontSize
|
||||
|
||||
extension NSString {
|
||||
@nonobjc
|
||||
func size(attributes attrs: [NSAttributedStringKey: Any]? = nil) -> NSSize {
|
||||
return size(withAttributes: attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSScreen {
|
||||
var mScale: CGFloat {
|
||||
return self.backingScaleFactor
|
||||
|
||||
func MMainScreen() -> MScreen? {
|
||||
return MScreen.main
|
||||
}
|
||||
}
|
||||
|
||||
extension NSImage {
|
||||
var cgImage: CGImage? {
|
||||
return self.cgImage(forProposedRect: nil, context: nil, hints: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSTouch {
|
||||
func location(in view: NSView) -> NSPoint {
|
||||
let n = self.normalizedPosition
|
||||
let b = view.bounds
|
||||
return NSPoint(x: b.origin.x + b.size.width * n.x, y: b.origin.y + b.size.height * n.y)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSString {
|
||||
@nonobjc
|
||||
func size(attributes attrs: [NSAttributedStringKey : Any]? = nil) -> NSSize {
|
||||
return size(withAttributes: attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func MMainScreen() -> MScreen? {
|
||||
return MScreen.main
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -9,88 +9,87 @@
|
||||
import Foundation
|
||||
|
||||
#if os(OSX)
|
||||
import AppKit
|
||||
|
||||
private var imageContextStack: [CGFloat] = []
|
||||
|
||||
func MGraphicsGetCurrentContext() -> CGContext? {
|
||||
return NSGraphicsContext.current?.cgContext
|
||||
}
|
||||
|
||||
func MGraphicsPushContext(_ context: CGContext) {
|
||||
let cx = NSGraphicsContext(cgContext: context, flipped: true)
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.current = cx
|
||||
}
|
||||
|
||||
func MGraphicsPopContext() {
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
}
|
||||
|
||||
func MImagePNGRepresentation(_ image: MImage) -> Data? {
|
||||
image.lockFocus()
|
||||
let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height))
|
||||
image.unlockFocus()
|
||||
|
||||
return rep?.representation(using: NSBitmapImageRep.FileType.png, properties: [:])
|
||||
}
|
||||
|
||||
func MImageJPEGRepresentation(_ image: MImage, _ quality: CGFloat = 0.9) -> Data? {
|
||||
image.lockFocus()
|
||||
let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height))
|
||||
image.unlockFocus()
|
||||
|
||||
return rep?.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: quality])
|
||||
}
|
||||
|
||||
|
||||
func MGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) {
|
||||
var scale = scale
|
||||
|
||||
if scale == 0.0 {
|
||||
scale = NSScreen.main?.backingScaleFactor ?? 1.0
|
||||
import AppKit
|
||||
|
||||
private var imageContextStack: [CGFloat] = []
|
||||
|
||||
func MGraphicsGetCurrentContext() -> CGContext? {
|
||||
return NSGraphicsContext.current?.cgContext
|
||||
}
|
||||
|
||||
let width = Int(size.width * scale)
|
||||
let height = Int(size.height * scale)
|
||||
|
||||
if width > 0 && height > 0 {
|
||||
imageContextStack.append(scale)
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
|
||||
guard let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 4*width, space: colorSpace, bitmapInfo: (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) else {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(height)))
|
||||
ctx.scaleBy(x: scale, y: scale)
|
||||
MGraphicsPushContext(ctx)
|
||||
|
||||
func MGraphicsPushContext(_ context: CGContext) {
|
||||
let cx = NSGraphicsContext(cgContext: context, flipped: true)
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.current = cx
|
||||
}
|
||||
}
|
||||
|
||||
func MGraphicsGetImageFromCurrentImageContext() -> MImage? {
|
||||
if !imageContextStack.isEmpty {
|
||||
guard let ctx = MGraphicsGetCurrentContext() else {
|
||||
|
||||
func MGraphicsPopContext() {
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
}
|
||||
|
||||
func MImagePNGRepresentation(_ image: MImage) -> Data? {
|
||||
image.lockFocus()
|
||||
let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height))
|
||||
image.unlockFocus()
|
||||
|
||||
return rep?.representation(using: NSBitmapImageRep.FileType.png, properties: [:])
|
||||
}
|
||||
|
||||
func MImageJPEGRepresentation(_ image: MImage, _ quality: CGFloat = 0.9) -> Data? {
|
||||
image.lockFocus()
|
||||
let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height))
|
||||
image.unlockFocus()
|
||||
|
||||
return rep?.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: quality])
|
||||
}
|
||||
|
||||
func MGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) {
|
||||
var scale = scale
|
||||
|
||||
if scale == 0.0 {
|
||||
scale = NSScreen.main?.backingScaleFactor ?? 1.0
|
||||
}
|
||||
|
||||
let width = Int(size.width * scale)
|
||||
let height = Int(size.height * scale)
|
||||
|
||||
if width > 0 && height > 0 {
|
||||
imageContextStack.append(scale)
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
|
||||
guard let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 4 * width, space: colorSpace, bitmapInfo: (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) else {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(height)))
|
||||
ctx.scaleBy(x: scale, y: scale)
|
||||
MGraphicsPushContext(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func MGraphicsGetImageFromCurrentImageContext() -> MImage? {
|
||||
if !imageContextStack.isEmpty {
|
||||
guard let ctx = MGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let scale = imageContextStack.last!
|
||||
if let theCGImage = ctx.makeImage() {
|
||||
let size = CGSize(width: CGFloat(ctx.width) / scale, height: CGFloat(ctx.height) / scale)
|
||||
let image = NSImage(cgImage: theCGImage, size: size)
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let scale = imageContextStack.last!
|
||||
if let theCGImage = ctx.makeImage() {
|
||||
let size = CGSize(width: CGFloat(ctx.width) / scale, height: CGFloat(ctx.height) / scale)
|
||||
let image = NSImage(cgImage: theCGImage, size: size)
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MGraphicsEndImageContext() {
|
||||
if imageContextStack.last != nil {
|
||||
imageContextStack.removeLast()
|
||||
MGraphicsPopContext()
|
||||
|
||||
func MGraphicsEndImageContext() {
|
||||
if imageContextStack.last != nil {
|
||||
imageContextStack.removeLast()
|
||||
MGraphicsPopContext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -9,164 +9,162 @@
|
||||
import Foundation
|
||||
|
||||
#if os(OSX)
|
||||
import AppKit
|
||||
import AppKit
|
||||
|
||||
public struct MRectCorner: OptionSet {
|
||||
public let rawValue: UInt
|
||||
|
||||
public static let none = MRectCorner(rawValue: 0)
|
||||
public static let topLeft = MRectCorner(rawValue: 1 << 0)
|
||||
public static let topRight = MRectCorner(rawValue: 1 << 1)
|
||||
public static let bottomLeft = MRectCorner(rawValue: 1 << 2)
|
||||
public static let bottomRight = MRectCorner(rawValue: 1 << 3)
|
||||
public static var allCorners: MRectCorner {
|
||||
return [.topLeft, .topRight, .bottomLeft, .bottomRight]
|
||||
}
|
||||
|
||||
public init(rawValue: UInt) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension MBezierPath {
|
||||
|
||||
public var cgPath: CGPath {
|
||||
get { let path = CGMutablePath()
|
||||
var points = [CGPoint](repeating: .zero, count: 3)
|
||||
|
||||
for i in 0 ..< self.elementCount {
|
||||
let type = self.element(at: i, associatedPoints: &points)
|
||||
|
||||
switch type {
|
||||
case .moveToBezierPathElement:
|
||||
path.move(to: CGPoint(x: points[0].x, y: points[0].y))
|
||||
|
||||
case .lineToBezierPathElement:
|
||||
path.addLine(to: CGPoint(x: points[0].x, y: points[0].y))
|
||||
|
||||
case .curveToBezierPathElement:
|
||||
path.addCurve(
|
||||
to: CGPoint(x: points[2].x, y: points[2].y),
|
||||
control1: CGPoint(x: points[0].x, y: points[0].y),
|
||||
control2: CGPoint(x: points[1].x, y: points[1].y))
|
||||
|
||||
case .closePathBezierPathElement:
|
||||
path.closeSubpath()
|
||||
}
|
||||
public struct MRectCorner: OptionSet {
|
||||
public let rawValue: UInt
|
||||
|
||||
public static let none = MRectCorner(rawValue: 0)
|
||||
public static let topLeft = MRectCorner(rawValue: 1 << 0)
|
||||
public static let topRight = MRectCorner(rawValue: 1 << 1)
|
||||
public static let bottomLeft = MRectCorner(rawValue: 1 << 2)
|
||||
public static let bottomRight = MRectCorner(rawValue: 1 << 3)
|
||||
public static var allCorners: MRectCorner {
|
||||
return [.topLeft, .topRight, .bottomLeft, .bottomRight]
|
||||
}
|
||||
|
||||
public init(rawValue: UInt) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) {
|
||||
self.init()
|
||||
self.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
|
||||
|
||||
extension MBezierPath {
|
||||
|
||||
public var cgPath: CGPath {
|
||||
let path = CGMutablePath()
|
||||
var points = [CGPoint](repeating: .zero, count: 3)
|
||||
|
||||
for i in 0 ..< self.elementCount {
|
||||
let type = self.element(at: i, associatedPoints: &points)
|
||||
|
||||
switch type {
|
||||
case .moveToBezierPathElement:
|
||||
path.move(to: CGPoint(x: points[0].x, y: points[0].y))
|
||||
|
||||
case .lineToBezierPathElement:
|
||||
path.addLine(to: CGPoint(x: points[0].x, y: points[0].y))
|
||||
|
||||
case .curveToBezierPathElement:
|
||||
path.addCurve(
|
||||
to: CGPoint(x: points[2].x, y: points[2].y),
|
||||
control1: CGPoint(x: points[0].x, y: points[0].y),
|
||||
control2: CGPoint(x: points[1].x, y: points[1].y))
|
||||
|
||||
case .closePathBezierPathElement:
|
||||
path.closeSubpath()
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) {
|
||||
self.init()
|
||||
self.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
|
||||
}
|
||||
|
||||
public convenience init(roundedRect rect: NSRect, byRoundingCorners corners: MRectCorner, cornerRadii: NSSize) {
|
||||
self.init()
|
||||
|
||||
let kappa: CGFloat = 1.0 - 0.552228474
|
||||
|
||||
let topLeft = rect.origin
|
||||
let topRight = NSPoint(x: rect.maxX, y: rect.minY)
|
||||
let bottomRight = NSPoint(x: rect.maxX, y: rect.maxY)
|
||||
let bottomLeft = NSPoint(x: rect.minX, y: rect.maxY)
|
||||
|
||||
if corners.contains(.topLeft) {
|
||||
move(to: CGPoint(x: topLeft.x + cornerRadii.width, y: topLeft.y))
|
||||
|
||||
} else {
|
||||
move(to: topLeft)
|
||||
}
|
||||
|
||||
if corners.contains(.topRight) {
|
||||
line(to: CGPoint(x: topRight.x - cornerRadii.width, y: topRight.y))
|
||||
|
||||
curve(to: CGPoint(x: topRight.x, y: topRight.y + cornerRadii.height),
|
||||
controlPoint1: CGPoint(x: topRight.x - cornerRadii.width * kappa, y: topRight.y),
|
||||
controlPoint2: CGPoint(x: topRight.x, y: topRight.y + cornerRadii.height * kappa))
|
||||
|
||||
} else {
|
||||
line(to: topRight)
|
||||
}
|
||||
|
||||
if corners.contains(.bottomRight) {
|
||||
line(to: CGPoint(x: bottomRight.x, y: bottomRight.y - cornerRadii.height))
|
||||
|
||||
curve(to: CGPoint(x: bottomRight.x - cornerRadii.width, y: bottomRight.y),
|
||||
controlPoint1: CGPoint(x: bottomRight.x, y: bottomRight.y - cornerRadii.height * kappa),
|
||||
controlPoint2: CGPoint(x: bottomRight.x - cornerRadii.width * kappa, y: bottomRight.y))
|
||||
|
||||
} else {
|
||||
line(to: bottomRight)
|
||||
}
|
||||
|
||||
if corners.contains(.bottomLeft) {
|
||||
line(to: CGPoint(x: bottomLeft.x + cornerRadii.width, y: bottomLeft.y))
|
||||
|
||||
curve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y - cornerRadii.height),
|
||||
controlPoint1: CGPoint(x: bottomLeft.x + cornerRadii.width * kappa, y: bottomLeft.y),
|
||||
controlPoint2: CGPoint(x: bottomLeft.x, y: bottomLeft.y - cornerRadii.height * kappa))
|
||||
|
||||
} else {
|
||||
line(to: bottomLeft)
|
||||
}
|
||||
|
||||
if corners.contains(.topLeft) {
|
||||
line(to: CGPoint(x: topLeft.x, y: topLeft.y + cornerRadii.height))
|
||||
|
||||
curve(to: CGPoint(x: topLeft.x + cornerRadii.width, y: topLeft.y),
|
||||
controlPoint1: CGPoint(x: topLeft.x, y: topLeft.y + cornerRadii.height * kappa),
|
||||
controlPoint2: CGPoint(x: topLeft.x + cornerRadii.width * kappa, y: topLeft.y))
|
||||
|
||||
} else {
|
||||
line(to: topLeft)
|
||||
}
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
func reversing() -> MBezierPath {
|
||||
return self.reversed
|
||||
}
|
||||
|
||||
func addLine(to: NSPoint) {
|
||||
self.line(to: to)
|
||||
}
|
||||
|
||||
func addCurve(to: NSPoint, controlPoint1: NSPoint, controlPoint2: NSPoint) {
|
||||
self.curve(to: to, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
}
|
||||
|
||||
func addQuadCurveToPoint(endPoint: NSPoint, controlPoint: NSPoint) {
|
||||
let QP0 = self.currentPoint
|
||||
let CP3 = endPoint
|
||||
|
||||
let CP1 = CGPoint(
|
||||
x: QP0.x + ((2.0 / 3.0) * (controlPoint.x - QP0.x)),
|
||||
y: QP0.y + ((2.0 / 3.0) * (controlPoint.y - QP0.y))
|
||||
)
|
||||
|
||||
let CP2 = CGPoint(
|
||||
x: endPoint.x + (2.0 / 3.0) * (controlPoint.x - endPoint.x),
|
||||
y: endPoint.y + (2.0 / 3.0) * (controlPoint.y - endPoint.y)
|
||||
)
|
||||
|
||||
self.addCurve(to: CP3, controlPoint1: CP1, controlPoint2: CP2)
|
||||
}
|
||||
|
||||
func addArc(withCenter: NSPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) {
|
||||
let startAngleRadian = ((startAngle) * (180.0 / .pi))
|
||||
let endAngleRadian = ((endAngle) * (180.0 / .pi))
|
||||
self.appendArc(withCenter: withCenter, radius: radius, startAngle: startAngleRadian, endAngle: endAngleRadian, clockwise: !clockwise)
|
||||
}
|
||||
|
||||
func addPath(path: NSBezierPath!) {
|
||||
self.append(path)
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init(roundedRect rect: NSRect, byRoundingCorners corners: MRectCorner, cornerRadii: NSSize) {
|
||||
self.init()
|
||||
|
||||
let kappa: CGFloat = 1.0 - 0.552228474
|
||||
|
||||
let topLeft = rect.origin
|
||||
let topRight = NSPoint(x: rect.maxX, y: rect.minY);
|
||||
let bottomRight = NSPoint(x: rect.maxX, y: rect.maxY);
|
||||
let bottomLeft = NSPoint(x: rect.minX, y: rect.maxY);
|
||||
|
||||
if corners.contains(.topLeft) {
|
||||
move(to: CGPoint(x: topLeft.x + cornerRadii.width, y: topLeft.y))
|
||||
|
||||
} else {
|
||||
move(to: topLeft)
|
||||
}
|
||||
|
||||
if corners.contains(.topRight) {
|
||||
line(to: CGPoint(x: topRight.x - cornerRadii.width, y: topRight.y))
|
||||
|
||||
curve(to: CGPoint(x: topRight.x, y: topRight.y + cornerRadii.height),
|
||||
controlPoint1: CGPoint(x: topRight.x - cornerRadii.width * kappa, y: topRight.y),
|
||||
controlPoint2: CGPoint(x: topRight.x, y: topRight.y + cornerRadii.height * kappa))
|
||||
|
||||
|
||||
} else {
|
||||
line(to: topRight)
|
||||
}
|
||||
|
||||
if corners.contains(.bottomRight) {
|
||||
line(to: CGPoint(x: bottomRight.x, y: bottomRight.y - cornerRadii.height))
|
||||
|
||||
curve(to: CGPoint(x: bottomRight.x - cornerRadii.width, y: bottomRight.y),
|
||||
controlPoint1: CGPoint(x: bottomRight.x, y: bottomRight.y - cornerRadii.height * kappa),
|
||||
controlPoint2: CGPoint(x: bottomRight.x - cornerRadii.width * kappa, y: bottomRight.y))
|
||||
|
||||
} else {
|
||||
line(to: bottomRight)
|
||||
}
|
||||
|
||||
if corners.contains(.bottomLeft) {
|
||||
line(to: CGPoint(x: bottomLeft.x + cornerRadii.width, y: bottomLeft.y))
|
||||
|
||||
curve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y - cornerRadii.height),
|
||||
controlPoint1: CGPoint(x: bottomLeft.x + cornerRadii.width * kappa, y: bottomLeft.y),
|
||||
controlPoint2: CGPoint(x: bottomLeft.x, y: bottomLeft.y - cornerRadii.height * kappa))
|
||||
|
||||
} else {
|
||||
line(to: bottomLeft)
|
||||
}
|
||||
|
||||
if corners.contains(.topLeft) {
|
||||
line(to: CGPoint(x: topLeft.x, y: topLeft.y + cornerRadii.height))
|
||||
|
||||
curve(to: CGPoint(x: topLeft.x + cornerRadii.width, y: topLeft.y),
|
||||
controlPoint1: CGPoint(x: topLeft.x, y: topLeft.y + cornerRadii.height * kappa),
|
||||
controlPoint2: CGPoint(x: topLeft.x + cornerRadii.width * kappa, y: topLeft.y))
|
||||
|
||||
} else {
|
||||
line(to: topLeft)
|
||||
}
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
func reversing() -> MBezierPath {
|
||||
return self.reversed
|
||||
}
|
||||
|
||||
func addLine(to: NSPoint) {
|
||||
self.line(to: to)
|
||||
}
|
||||
|
||||
func addCurve(to: NSPoint, controlPoint1: NSPoint, controlPoint2: NSPoint) {
|
||||
self.curve(to: to, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
}
|
||||
|
||||
func addQuadCurveToPoint(endPoint: NSPoint, controlPoint: NSPoint) {
|
||||
let QP0 = self.currentPoint
|
||||
let CP3 = endPoint
|
||||
|
||||
let CP1 = CGPoint(
|
||||
x: QP0.x + ((2.0 / 3.0) * (controlPoint.x - QP0.x)),
|
||||
y: QP0.y + ((2.0 / 3.0) * (controlPoint.y - QP0.y))
|
||||
)
|
||||
|
||||
let CP2 = CGPoint(
|
||||
x: endPoint.x + (2.0 / 3.0) * (controlPoint.x - endPoint.x),
|
||||
y: endPoint.y + (2.0 / 3.0) * (controlPoint.y - endPoint.y)
|
||||
)
|
||||
|
||||
self.addCurve(to: CP3, controlPoint1: CP1, controlPoint2: CP2)
|
||||
}
|
||||
|
||||
func addArc(withCenter: NSPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) {
|
||||
let startAngleRadian = ((startAngle) * (180.0 / .pi))
|
||||
let endAngleRadian = ((endAngle) * (180.0 / .pi))
|
||||
self.appendArc(withCenter: withCenter, radius: radius, startAngle: startAngleRadian, endAngle: endAngleRadian, clockwise: !clockwise)
|
||||
}
|
||||
|
||||
func addPath(path: NSBezierPath!) {
|
||||
self.append(path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -10,37 +10,37 @@ import Foundation
|
||||
|
||||
#if os(OSX)
|
||||
import AppKit
|
||||
|
||||
|
||||
public class MDisplayLink: MDisplayLinkProtocol {
|
||||
private var displayLink: CVDisplayLink?
|
||||
private var onUpdate: (() -> Void)?
|
||||
|
||||
|
||||
// MARK: - Lifecycle
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MDisplayLinkProtocol
|
||||
func startUpdates(_ onUpdate: @escaping () -> Void) {
|
||||
self.onUpdate = onUpdate
|
||||
|
||||
|
||||
if CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) != kCVReturnSuccess {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
CVDisplayLinkSetOutputCallback(displayLink!, { (_, _, _, _, _, userData) -> CVReturn in
|
||||
|
||||
|
||||
let `self` = unsafeBitCast(userData, to: MDisplayLink.self)
|
||||
`self`.onUpdate?()
|
||||
|
||||
|
||||
return kCVReturnSuccess
|
||||
}, Unmanaged.passUnretained(self).toOpaque())
|
||||
|
||||
|
||||
if displayLink != nil {
|
||||
CVDisplayLinkStart(displayLink!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func invalidate() {
|
||||
stop()
|
||||
}
|
||||
@ -51,5 +51,5 @@ import Foundation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -9,192 +9,189 @@
|
||||
import Foundation
|
||||
|
||||
#if os(OSX)
|
||||
import AppKit
|
||||
|
||||
public enum MViewContentMode: Int {
|
||||
case scaleToFill
|
||||
case scaleAspectFit
|
||||
case scaleAspectFill
|
||||
case redraw
|
||||
case center
|
||||
case top
|
||||
case bottom
|
||||
case left
|
||||
case right
|
||||
case topLeft
|
||||
case topRight
|
||||
case bottomLeft
|
||||
case bottomRight
|
||||
}
|
||||
|
||||
open class MView: NSView, Touchable {
|
||||
public override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
|
||||
self.wantsLayer = true
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
|
||||
self.wantsLayer = true
|
||||
setupMouse()
|
||||
}
|
||||
|
||||
open override var isFlipped: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var mGestureRecognizers: [NSGestureRecognizer]? {
|
||||
return self.gestureRecognizers
|
||||
}
|
||||
|
||||
open var backgroundColor: MColor? {
|
||||
get {
|
||||
return self.layer?.backgroundColor == nil ? nil : NSColor(cgColor: self.layer!.backgroundColor!)
|
||||
}
|
||||
|
||||
set {
|
||||
self.layer?.backgroundColor = newValue == nil ? nil : newValue?.cgColor ?? MColor.black.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
var mLayer: CALayer? {
|
||||
return self.layer
|
||||
}
|
||||
|
||||
var contentMode: MViewContentMode = .scaleToFill
|
||||
|
||||
func removeGestureRecognizers() {
|
||||
self.gestureRecognizers.removeAll()
|
||||
}
|
||||
|
||||
func didMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
}
|
||||
|
||||
func setNeedsDisplay() {
|
||||
self.setNeedsDisplay(self.bounds)
|
||||
}
|
||||
|
||||
func layoutSubviews() {
|
||||
super.resizeSubviews(withOldSize: self.bounds.size)
|
||||
}
|
||||
|
||||
// MARK: - Touch pad
|
||||
open override func touchesBegan(with event: NSEvent) {
|
||||
super.touchesBegan(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesBegan(touchPoints)
|
||||
}
|
||||
|
||||
open override func touchesEnded(with event: NSEvent) {
|
||||
super.touchesEnded(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesEnded(touchPoints)
|
||||
}
|
||||
|
||||
open override func touchesMoved(with event: NSEvent) {
|
||||
super.touchesMoved(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesMoved(touchPoints)
|
||||
}
|
||||
|
||||
open override func touchesCancelled(with event: NSEvent) {
|
||||
super.touchesCancelled(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesCancelled(touchPoints)
|
||||
}
|
||||
|
||||
// MARK: - Mouse
|
||||
private func setupMouse() {
|
||||
subscribeForMouseDown()
|
||||
subscribeForMouseUp()
|
||||
subscribeForMouseDragged()
|
||||
}
|
||||
|
||||
private func subscribeForMouseDown() {
|
||||
NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown, handler: { [weak self] event -> NSEvent? in
|
||||
self?.handleInput(event: event, handler: { touches in
|
||||
self?.mTouchesBegan(touches)
|
||||
})
|
||||
|
||||
return event
|
||||
})
|
||||
}
|
||||
|
||||
private func subscribeForMouseUp() {
|
||||
NSEvent.addLocalMonitorForEvents(matching: .leftMouseUp, handler: { [weak self] event -> NSEvent? in
|
||||
self?.handleInput(event: event, handler: { touches in
|
||||
self?.mTouchesEnded(touches)
|
||||
})
|
||||
|
||||
return event
|
||||
})
|
||||
import AppKit
|
||||
|
||||
public enum MViewContentMode: Int {
|
||||
case scaleToFill = 0
|
||||
case scaleAspectFit = 1
|
||||
case scaleAspectFill = 2
|
||||
case redraw = 3
|
||||
case center = 4
|
||||
case top = 5
|
||||
case bottom = 6
|
||||
case left = 7
|
||||
case right = 8
|
||||
case topLeft = 9
|
||||
case topRight = 10
|
||||
case bottomLeft = 11
|
||||
case bottomRight = 12
|
||||
}
|
||||
|
||||
private func subscribeForMouseDragged() {
|
||||
NSEvent.addLocalMonitorForEvents(matching: .leftMouseDragged, handler: { [weak self] event -> NSEvent? in
|
||||
self?.handleInput(event: event, handler: { touches in
|
||||
self?.mTouchesMoved(touches)
|
||||
})
|
||||
|
||||
return event
|
||||
})
|
||||
open class MView: NSView, Touchable {
|
||||
public override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
|
||||
self.wantsLayer = true
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
|
||||
self.wantsLayer = true
|
||||
setupMouse()
|
||||
}
|
||||
|
||||
open override var isFlipped: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var mGestureRecognizers: [NSGestureRecognizer]? {
|
||||
return self.gestureRecognizers
|
||||
}
|
||||
|
||||
open var backgroundColor: MColor? {
|
||||
get {
|
||||
return self.layer?.backgroundColor == nil ? nil : NSColor(cgColor: self.layer!.backgroundColor!)
|
||||
}
|
||||
|
||||
set {
|
||||
self.layer?.backgroundColor = newValue == nil ? nil : newValue?.cgColor ?? MColor.black.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
var mLayer: CALayer? {
|
||||
return self.layer
|
||||
}
|
||||
|
||||
var contentMode: MViewContentMode = .scaleToFill
|
||||
|
||||
func removeGestureRecognizers() {
|
||||
self.gestureRecognizers.removeAll()
|
||||
}
|
||||
|
||||
func didMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
}
|
||||
|
||||
func setNeedsDisplay() {
|
||||
self.setNeedsDisplay(self.bounds)
|
||||
}
|
||||
|
||||
func layoutSubviews() {
|
||||
super.resizeSubviews(withOldSize: self.bounds.size)
|
||||
}
|
||||
|
||||
// MARK: - Touch pad
|
||||
open override func touchesBegan(with event: NSEvent) {
|
||||
super.touchesBegan(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesBegan(touchPoints)
|
||||
}
|
||||
|
||||
open override func touchesEnded(with event: NSEvent) {
|
||||
super.touchesEnded(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesEnded(touchPoints)
|
||||
}
|
||||
|
||||
open override func touchesMoved(with event: NSEvent) {
|
||||
super.touchesMoved(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesMoved(touchPoints)
|
||||
}
|
||||
|
||||
open override func touchesCancelled(with event: NSEvent) {
|
||||
super.touchesCancelled(with: event)
|
||||
|
||||
let touchPoints = event.touches(matching: .any, in: self).map { touch -> MTouchEvent in
|
||||
let location = touch.location(in: self)
|
||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||
|
||||
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||
}
|
||||
|
||||
mTouchesCancelled(touchPoints)
|
||||
}
|
||||
|
||||
// MARK: - Mouse
|
||||
private func setupMouse() {
|
||||
subscribeForMouseDown()
|
||||
subscribeForMouseUp()
|
||||
subscribeForMouseDragged()
|
||||
}
|
||||
|
||||
private func subscribeForMouseDown() {
|
||||
NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { [weak self] event -> NSEvent? in
|
||||
self?.handleInput(event: event) { touches in
|
||||
self?.mTouchesBegan(touches)
|
||||
}
|
||||
return event
|
||||
}
|
||||
}
|
||||
|
||||
private func subscribeForMouseUp() {
|
||||
NSEvent.addLocalMonitorForEvents(matching: .leftMouseUp) { [weak self] event -> NSEvent? in
|
||||
self?.handleInput(event: event) { touches in
|
||||
self?.mTouchesEnded(touches)
|
||||
}
|
||||
return event
|
||||
}
|
||||
}
|
||||
|
||||
private func subscribeForMouseDragged() {
|
||||
NSEvent.addLocalMonitorForEvents(matching: .leftMouseDragged) { [weak self] event -> NSEvent? in
|
||||
self?.handleInput(event: event) { touches in
|
||||
self?.mTouchesMoved(touches)
|
||||
}
|
||||
return event
|
||||
}
|
||||
}
|
||||
|
||||
private func handleInput(event: NSEvent, handler: (_ touches: [MTouchEvent]) -> Void ) {
|
||||
let location = self.convert(event.locationInWindow, to: .none)
|
||||
let touchPoint = MTouchEvent(x: Double(location.x), y: Double(location.y), id: 0)
|
||||
|
||||
handler([touchPoint])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MARK: - Touchable
|
||||
func mTouchesBegan(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
|
||||
func mTouchesMoved(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
|
||||
func mTouchesEnded(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
|
||||
func mTouchesCancelled(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func handleInput(event: NSEvent, handler: (_ touches: [MTouchEvent]) -> Void ) {
|
||||
let location = self.convert(event.locationInWindow, to: .none)
|
||||
let touchPoint = MTouchEvent(x: Double(location.x), y: Double(location.y), id: 0)
|
||||
|
||||
handler([touchPoint])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MARK: - Touchable
|
||||
func mTouchesBegan(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
|
||||
func mTouchesMoved(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
|
||||
func mTouchesEnded(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
|
||||
func mTouchesCancelled(_ touches: [MTouchEvent]) {
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -1,79 +1,79 @@
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
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
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
group.contentsVar.onChange { [weak self] _ in
|
||||
self?.updateRenderers()
|
||||
|
||||
override func doAddObservers() {
|
||||
super.doAddObservers()
|
||||
|
||||
guard let group = group else {
|
||||
return
|
||||
}
|
||||
|
||||
group.contentsVar.onChange { [weak self] _ in
|
||||
self?.updateRenderers()
|
||||
}
|
||||
observe(group.contentsVar)
|
||||
}
|
||||
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 node() -> Node? {
|
||||
return group
|
||||
}
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
for renderer in renderers.reversed() {
|
||||
if let node = renderer.findNodeAt(location: location, ctx: ctx) {
|
||||
return node
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
renderers.forEach { renderer in
|
||||
renderer.render(force: force, opacity: opacity)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,181 +5,181 @@ import Foundation
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
override func node() -> Node? {
|
||||
return image
|
||||
}
|
||||
|
||||
var mImage: MImage?
|
||||
if image.src.contains("memory") {
|
||||
let id = image.src.replacingOccurrences(of: "memory://", with: "")
|
||||
mImage = imagesMap[id]
|
||||
} else {
|
||||
mImage = image.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)
|
||||
}
|
||||
|
||||
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 doRender(_ force: Bool, opacity: Double) {
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
guard let image = image else {
|
||||
return .none
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
let osImage = MImage(named: image.src)
|
||||
#elseif os(OSX)
|
||||
let osImage = MImage(named: NSImage.Name(rawValue: image.src))
|
||||
#endif
|
||||
|
||||
if let mImage = osImage {
|
||||
let rect = getRect(mImage)
|
||||
if rect.contains(location) {
|
||||
return node()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
let osImage = MImage(named: image.src)
|
||||
#elseif os(OSX)
|
||||
let osImage = MImage(named: NSImage.Name(rawValue: image.src))
|
||||
#endif
|
||||
|
||||
if let mImage = osImage {
|
||||
let rect = getRect(mImage)
|
||||
if (rect.contains(location)) {
|
||||
return node()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func getRect(_ mImage: MImage) -> CGRect {
|
||||
guard let image = image else {
|
||||
return .zero
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user