Files
Macaw/Source/model/scene/Node.swift

334 lines
10 KiB
Swift

import Foundation
open class Node: Drawable {
public let placeVar: AnimatableVariable<Transform>
open var place: Transform {
get { return placeVar.value }
set(val) { placeVar.value = val }
}
public let opaqueVar: Variable<Bool>
open var opaque: Bool {
get { return opaqueVar.value }
set(val) { opaqueVar.value = val }
}
public let opacityVar: AnimatableVariable<Double>
open var opacity: Double {
get { return opacityVar.value }
set(val) { opacityVar.value = val }
}
public let clipVar: Variable<Locus?>
open var clip: Locus? {
get { return clipVar.value }
set(val) { clipVar.value = val }
}
public let maskVar: Variable<Node?>
open var mask: Node? {
get { return maskVar.value }
set(val) { maskVar.value = val }
}
public let effectVar: Variable<Effect?>
open var effect: Effect? {
get { return effectVar.value }
set(val) { effectVar.value = val }
}
var animations: [BasicAnimation] = []
// MARK: - Searching
public func nodeBy(tag: String) -> Node? {
return nodeBy(predicate: { $0.tag.contains(tag) })
}
public func nodesBy(tag: String) -> [Node] {
return nodesBy(predicate: { $0.tag.contains(tag) })
}
public func nodeBy(predicate: (Node) -> Bool) -> Node? {
if predicate(self) {
return self
}
return .none
}
public func nodesBy(predicate: (Node) -> Bool) -> [Node] {
return [nodeBy(predicate: predicate)].compactMap { $0 }
}
// MARK: - Events
internal var animationObservers = [AnimationObserver]()
var touchPressedHandlers = [ChangeHandler<TouchEvent>]()
var touchMovedHandlers = [ChangeHandler<TouchEvent>]()
var touchReleasedHandlers = [ChangeHandler<TouchEvent>]()
var prevTouchCount: Int = 0
var prevTouchTimer: Timer?
var isLongTapInProgress = false
var tapHandlers = [Int: [ChangeHandler<TapEvent>]]()
var longTapHandlers = [ChangeHandler<TapEvent>]()
var panHandlers = [ChangeHandler<PanEvent>]()
var rotateHandlers = [ChangeHandler<RotateEvent>]()
var pinchHandlers = [ChangeHandler<PinchEvent>]()
@discardableResult public func onTouchPressed (_ f: @escaping (TouchEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TouchEvent>(f)
touchPressedHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.touchPressedHandlers.firstIndex(of: handler) else {
return
}
self?.touchPressedHandlers.remove(at: index)
}
}
@discardableResult public func onTouchMoved(_ f: @escaping (TouchEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TouchEvent>(f)
touchMovedHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.touchMovedHandlers.firstIndex(of: handler) else {
return
}
self?.touchMovedHandlers.remove(at: index)
}
}
@discardableResult public func onTouchReleased(_ f: @escaping (TouchEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TouchEvent>(f)
touchReleasedHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.touchReleasedHandlers.firstIndex(of: handler) else {
return
}
self?.touchReleasedHandlers.remove(at: index)
}
}
@discardableResult public func onTap(tapCount: Int = 1, f: @escaping (TapEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TapEvent>(f)
var handlers = tapHandlers[tapCount] ?? []
handlers.append(handler)
tapHandlers[tapCount] = handlers
return Disposable { [weak self, unowned handler] in
guard let index = self?.tapHandlers[tapCount]?.firstIndex(of: handler) else {
return
}
self?.tapHandlers[tapCount]?.remove(at: index)
}
}
@discardableResult public func onLongTap(_ f: @escaping (TapEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TapEvent>(f)
longTapHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.longTapHandlers.firstIndex(of: handler) else {
return
}
self?.longTapHandlers.remove(at: index)
}
}
@discardableResult public func onPan(_ f: @escaping (PanEvent) -> Void) -> Disposable {
let handler = ChangeHandler<PanEvent>(f)
panHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.panHandlers.firstIndex(of: handler) else {
return
}
self?.panHandlers.remove(at: index)
}
}
@discardableResult public func onRotate(_ f: @escaping (RotateEvent) -> Void) -> Disposable {
let handler = ChangeHandler<RotateEvent>(f)
rotateHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.rotateHandlers.firstIndex(of: handler) else {
return
}
self?.rotateHandlers.remove(at: index)
}
}
@discardableResult public func onPinch(_ f: @escaping (PinchEvent) -> Void) -> Disposable {
let handler = ChangeHandler<PinchEvent>(f)
pinchHandlers.append(handler)
return Disposable { [weak self, unowned handler] in
guard let index = self?.pinchHandlers.firstIndex(of: handler) else {
return
}
self?.pinchHandlers.remove(at: index)
}
}
// Helpers
func handleTouchPressed(_ event: TouchEvent) {
touchPressedHandlers.forEach { handler in handler.handle(event) }
}
func handleTouchReleased(_ event: TouchEvent) {
touchReleasedHandlers.forEach { handler in handler.handle(event) }
}
func handleTouchMoved(_ event: TouchEvent) {
touchMovedHandlers.forEach { handler in handler.handle(event) }
}
// MARK: - Multiple tap handling
func handleTap( _ event: TapEvent ) {
if isLongTapInProgress {
prevTouchCount = 0
return
}
if prevTouchTimer != nil {
prevTouchTimer?.invalidate()
prevTouchTimer = nil
}
prevTouchCount += 1
for tapCount in tapHandlers.keys where tapCount > prevTouchCount {
// wait some more - there is a recognizer for even more taps
prevTouchTimer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(onTouchTimer), userInfo: event, repeats: false)
return
}
for (tapCount, handlers) in tapHandlers where tapCount == prevTouchCount {
// nothing to wait for - max tap count reached
handlers.forEach { handler in handler.handle(event) }
prevTouchCount = 0
}
}
@objc func onTouchTimer(timer: Timer) {
if isLongTapInProgress {
prevTouchCount = 0
return
}
for touchCount in tapHandlers.keys.sorted(by: { $0>$1 }) {
if touchCount <= prevTouchCount, let event = timer.userInfo as? TapEvent {
// no more taps coming, settle for next best thing
for _ in 0..<prevTouchCount / touchCount { // might need to call it multiple times
tapHandlers[touchCount]?.forEach { handler in handler.handle(event) }
}
break
}
}
prevTouchCount = 0
}
// MARK: - Helpers
func handleLongTap( _ event: TapEvent, touchBegan: Bool ) {
isLongTapInProgress = touchBegan
if touchBegan {
return
}
longTapHandlers.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.isEmpty
}
func shouldCheckForMoved() -> Bool {
return !touchMovedHandlers.isEmpty
}
func shouldCheckForReleased() -> Bool {
return !touchReleasedHandlers.isEmpty
}
func shouldCheckForTap() -> Bool {
return !tapHandlers.isEmpty
}
func shouldCheckForLongTap() -> Bool {
return !longTapHandlers.isEmpty
}
func shouldCheckForPan() -> Bool {
return !panHandlers.isEmpty
}
func shouldCheckForRotate() -> Bool {
return !rotateHandlers.isEmpty
}
func shouldCheckForPinch() -> Bool {
return !pinchHandlers.isEmpty
}
public init(place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, mask: Node? = 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.maskVar = Variable<Node?>(mask)
self.effectVar = Variable<Effect?>(effect)
super.init(
visible: visible,
tag: tag
)
self.placeVar.node = self
self.opacityVar.node = self
}
open var bounds: Rect? {
return .none
}
// MARK: - Hash
override open var hash: Int {
var hasher = Hasher()
hasher.combine(Unmanaged.passUnretained(self).toOpaque())
return hasher.finalize()
}
override open func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Node else {
return false
}
return Unmanaged.passUnretained(self).toOpaque() == Unmanaged.passUnretained(object).toOpaque()
}
}