1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-09-19 08:57:35 +03:00
Macaw/Source/views/MacawView.swift

487 lines
14 KiB
Swift
Raw Permalink Normal View History

import Foundation
2017-08-10 16:01:11 +03:00
2017-08-10 14:26:02 +03:00
#if os(iOS)
import UIKit
2017-08-10 16:01:11 +03:00
#elseif os(OSX)
import AppKit
2017-08-10 14:26:02 +03:00
#endif
2016-08-16 16:45:09 +03:00
///
/// MacawView is a main class used to embed Macaw scene into your Cocoa UI.
/// You could create your own view extended from MacawView with predefined scene.
///
2017-08-10 16:01:11 +03:00
open class MacawView: MView, MGestureRecognizerDelegate {
/// Scene root node
open var node: Node = Group() {
willSet {
nodesMap.remove(node)
}
2016-11-29 09:45:49 +03:00
2017-08-10 16:01:11 +03:00
didSet {
nodesMap.add(node, view: self)
self.renderer?.dispose()
if let cache = animationCache {
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
}
if let _ = superview {
animationProducer.addStoredAnimations(node)
}
self.setNeedsDisplay()
}
}
override open var frame: CGRect {
didSet {
super.frame = frame
frameSetFirstTime = true
guard let _ = superview else {
return
}
animationProducer.addStoredAnimations(node)
2016-11-29 09:45:49 +03:00
}
2017-08-10 16:01:11 +03:00
}
override open func didMoveToSuperview() {
super.didMoveToSuperview()
2016-11-29 09:45:49 +03:00
2017-08-10 16:01:11 +03:00
if !frameSetFirstTime {
return
2016-11-29 09:45:49 +03:00
}
2017-08-10 16:01:11 +03:00
animationProducer.addStoredAnimations(node)
}
2017-08-23 08:29:45 +03:00
var touchesMap = [MTouchEvent: [Node]]()
var touchesOfNode = [Node: [MTouchEvent]]()
2017-08-10 16:01:11 +03:00
var recognizersMap = [MGestureRecognizer: [Node]]()
var context: RenderContext!
var renderer: NodeRenderer?
var toRender = true
var frameSetFirstTime = false
internal var animationCache: AnimationCache?
2017-08-15 11:42:05 +03:00
#if os(OSX)
open override var layer: CALayer? {
didSet {
guard self.layer != nil else {
return
}
initializeView()
if let cache = self.animationCache {
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
}
}
}
#endif
2017-08-10 16:01:11 +03:00
public init?(node: Node, coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
2017-08-11 13:47:02 +03:00
2017-08-10 16:01:11 +03:00
initializeView()
self.node = node
nodesMap.add(node, view: self)
if let cache = self.animationCache {
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
}
}
public convenience init(node: Node, frame: CGRect) {
self.init(frame:frame)
self.node = node
nodesMap.add(node, view: self)
if let cache = self.animationCache {
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
initializeView()
}
public convenience required init?(coder aDecoder: NSCoder) {
self.init(node: Group(), coder: aDecoder)
}
fileprivate func initializeView() {
self.context = RenderContext(view: self)
guard let layer = self.mLayer else {
return
2016-11-29 09:45:49 +03:00
}
2017-08-10 16:01:11 +03:00
self.animationCache = AnimationCache(sceneLayer: layer)
let tapRecognizer = MTapGestureRecognizer(target: self, action: #selector(MacawView.handleTap))
let panRecognizer = MPanGestureRecognizer(target: self, action: #selector(MacawView.handlePan))
let rotationRecognizer = MRotationGestureRecognizer(target: self, action: #selector(MacawView.handleRotation))
let pinchRecognizer = MPinchGestureRecognizer(target: self, action: #selector(MacawView.handlePinch))
tapRecognizer.delegate = self
panRecognizer.delegate = self
rotationRecognizer.delegate = self
pinchRecognizer.delegate = self
tapRecognizer.cancelsTouchesInView = false
panRecognizer.cancelsTouchesInView = false
rotationRecognizer.cancelsTouchesInView = false
pinchRecognizer.cancelsTouchesInView = false
self.removeGestureRecognizers()
2017-08-10 16:01:11 +03:00
self.addGestureRecognizer(tapRecognizer)
self.addGestureRecognizer(panRecognizer)
self.addGestureRecognizer(rotationRecognizer)
self.addGestureRecognizer(pinchRecognizer)
}
2017-09-12 10:35:27 +03:00
override open func draw(_ rect: CGRect) {
let ctx = MGraphicsGetCurrentContext()
if self.backgroundColor == nil {
ctx?.clear(rect)
}
self.context.cgContext = ctx
renderer?.render(force: false, opacity: node.opacity)
2016-11-01 08:56:52 +03:00
}
2017-08-10 16:01:11 +03:00
private func localContext( _ callback: (CGContext) -> ()) {
MGraphicsBeginImageContextWithOptions(self.bounds.size, false, 1.0)
if let ctx = MGraphicsGetCurrentContext() {
callback(ctx)
}
MGraphicsEndImageContext()
}
// MARK: - Touches
2017-08-23 08:29:45 +03:00
override func mTouchesBegan(_ touches: [MTouchEvent]) {
2017-08-10 16:01:11 +03:00
if !self.node.shouldCheckForPressed() &&
!self.node.shouldCheckForMoved() &&
!self.node.shouldCheckForReleased (){
return
2016-11-29 09:45:49 +03:00
}
2016-11-01 08:56:52 +03:00
2017-08-10 16:01:11 +03:00
guard let renderer = renderer else {
return
2016-11-01 08:56:52 +03:00
}
2016-11-29 09:45:49 +03:00
2017-08-10 16:01:11 +03:00
for touch in touches {
2017-08-23 08:29:45 +03:00
let location = CGPoint(x: touch.x, y: touch.y)
2017-08-10 16:01:11 +03:00
var foundNode: Node? = .none
localContext { ctx in
foundNode = renderer.findNodeAt(location: location, ctx: ctx)
}
if let node = foundNode {
if touchesMap[touch] == nil {
touchesMap[touch] = [Node]()
}
let inverted = node.place.invert()!
let loc = location.applying(RenderUtils.mapTransform(inverted))
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
let point = TouchPoint(id: id, location: Point(x: Double(loc.x), y: Double(loc.y)))
let touchEvent = TouchEvent(node: node, points: [point])
var parent: Node? = node
while parent != .none {
let currentNode = parent!
if touchesOfNode[currentNode] == nil {
2017-08-23 08:29:45 +03:00
touchesOfNode[currentNode] = [MTouchEvent]()
2017-08-10 16:01:11 +03:00
}
touchesMap[touch]?.append(currentNode)
touchesOfNode[currentNode]?.append(touch)
parent!.handleTouchPressed(touchEvent)
parent = nodesMap.parents(parent!).first
}
}
}
}
2017-08-23 08:29:45 +03:00
override func mTouchesMoved(_ touches: [MTouchEvent]) {
if !self.node.shouldCheckForMoved() {
return
}
guard let _ = renderer else {
return
}
touchesOfNode.keys.forEach { currentNode in
guard let initialTouches = touchesOfNode[currentNode] else {
return
}
var points = [TouchPoint]()
for initialTouch in initialTouches {
let currentIndex = touches.index(of: initialTouch)!
let currentTouch = touches[currentIndex]
let location = CGPoint(x: currentTouch.x, y: currentTouch.y)
let inverted = currentNode.place.invert()!
let loc = location.applying(RenderUtils.mapTransform(inverted))
let point = TouchPoint(id: currentTouch.id, location: Point(x: Double(loc.x), y: Double(loc.y)))
points.append(point)
}
let touchEvent = TouchEvent(node: currentNode, points: points)
currentNode.handleTouchMoved(touchEvent)
}
2017-08-10 16:01:11 +03:00
}
2017-08-23 08:29:45 +03:00
override func mTouchesCancelled(_ touches: [MTouchEvent]) {
touchesEnded(touches: touches)
2017-08-10 16:01:11 +03:00
}
2017-08-23 08:29:45 +03:00
override func mTouchesEnded(_ touches: [MTouchEvent]) {
touchesEnded(touches: touches)
2017-08-10 16:01:11 +03:00
}
2017-08-23 08:29:45 +03:00
private func touchesEnded(touches: [MTouchEvent]) {
2017-08-10 16:01:11 +03:00
guard let _ = renderer else {
return
}
2017-02-28 13:41:29 +03:00
2017-08-10 16:01:11 +03:00
for touch in touches {
touchesMap[touch]?.forEach { node in
let inverted = node.place.invert()!
2017-08-23 08:29:45 +03:00
let location = CGPoint(x: touch.x, y: touch.y)
2017-08-10 16:01:11 +03:00
let loc = location.applying(RenderUtils.mapTransform(inverted))
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
let point = TouchPoint(id: id, location: Point(x: Double(loc.x), y: Double(loc.y)))
let touchEvent = TouchEvent(node: node, points: [point])
node.handleTouchReleased(touchEvent)
if let index = touchesOfNode[node]?.index(of: touch) {
touchesOfNode[node]?.remove(at: index)
if let count = touchesOfNode[node]?.count, count == 0 {
touchesOfNode.removeValue(forKey: node)
}
}
}
touchesMap.removeValue(forKey: touch)
}
}
// MARK: - Tap
func handleTap(recognizer: MTapGestureRecognizer) {
if !self.node.shouldCheckForTap() {
return
2016-11-29 09:45:49 +03:00
}
2017-08-10 16:01:11 +03:00
guard let renderer = renderer else {
return
}
let location = recognizer.location(in: self)
var foundNodes = [Node]()
localContext { ctx in
guard let foundNode = renderer.findNodeAt(location: location, ctx: ctx) else {
return
}
var parent: Node? = foundNode
while parent != .none {
if parent!.shouldCheckForTap() {
foundNodes.append(parent!)
2017-02-14 14:11:45 +03:00
}
2017-08-10 16:01:11 +03:00
parent = nodesMap.parents(parent!).first
}
2017-02-13 16:11:43 +03:00
}
2017-08-10 16:01:11 +03:00
foundNodes.forEach { node in
let inverted = node.place.invert()!
let loc = location.applying(RenderUtils.mapTransform(inverted))
let event = TapEvent(node: node, location: Point(x: Double(loc.x), y: Double(loc.y)))
node.handleTap(event)
}
}
// MARK: - Pan
func handlePan(recognizer: MPanGestureRecognizer) {
if !self.node.shouldCheckForPan() {
return
2017-02-13 16:11:43 +03:00
}
2017-08-10 16:01:11 +03:00
guard let renderer = renderer else {
return
2017-02-13 16:11:43 +03:00
}
2017-08-10 16:01:11 +03:00
if recognizer.state == .began {
let location = recognizer.location(in: self)
localContext { ctx in
guard let foundNode = renderer.findNodeAt(location: location, ctx: ctx) else {
return
}
2017-08-10 16:01:11 +03:00
if self.recognizersMap[recognizer] == nil {
self.recognizersMap[recognizer] = [Node]()
2017-02-14 14:11:45 +03:00
}
2017-08-10 16:01:11 +03:00
var parent: Node? = foundNode
while parent != .none {
if parent!.shouldCheckForPan() {
self.recognizersMap[recognizer]?.append(parent!)
}
parent = nodesMap.parents(parent!).first
2017-02-28 13:41:29 +03:00
}
2017-08-10 16:01:11 +03:00
}
2017-02-28 13:41:29 +03:00
}
2017-08-10 16:01:11 +03:00
// get the rotation and scale of the shape and apply to the translation
let translation = recognizer.translation(in: self)
recognizer.setTranslation(CGPoint.zero, in: self)
2017-02-28 13:41:29 +03:00
2017-08-10 16:01:11 +03:00
let transform = node.place
let rotation = -CGFloat(atan2f(Float(transform.m12), Float(transform.m11)))
let scale = CGFloat(sqrt(transform.m11 * transform.m11 + transform.m21 * transform.m21))
let translatedLocation = translation.applying(CGAffineTransform(rotationAngle: rotation))
2017-02-28 13:41:29 +03:00
2017-08-10 16:01:11 +03:00
recognizersMap[recognizer]?.forEach { node in
let event = PanEvent(node: node, dx: Double(translatedLocation.x / scale), dy: Double(translatedLocation.y / scale),
count: recognizer.mNumberOfTouches())
node.handlePan(event)
2017-03-01 12:38:41 +03:00
}
2017-08-10 16:01:11 +03:00
if recognizer.state == .ended || recognizer.state == .cancelled {
recognizersMap.removeValue(forKey: recognizer)
}
}
// MARK: - Rotation
func handleRotation(_ recognizer: MRotationGestureRecognizer) {
if !self.node.shouldCheckForRotate() {
return
2016-11-29 09:45:49 +03:00
}
2017-08-10 16:01:11 +03:00
guard let renderer = renderer else {
return
}
2017-02-28 13:41:29 +03:00
2017-08-10 16:01:11 +03:00
if recognizer.state == .began {
let location = recognizer.location(in: self)
localContext { ctx in
guard let foundNode = renderer.findNodeAt(location: location, ctx: ctx) else {
return
2017-02-28 13:41:29 +03:00
}
2017-08-10 16:01:11 +03:00
if self.recognizersMap[recognizer] == nil {
self.recognizersMap[recognizer] = [Node]()
2016-11-29 09:45:49 +03:00
}
2017-03-01 11:14:43 +03:00
2017-08-10 16:01:11 +03:00
var parent: Node? = foundNode
while parent != .none {
if parent!.shouldCheckForRotate() {
self.recognizersMap[recognizer]?.append(parent!)
}
parent = nodesMap.parents(parent!).first
2017-02-28 13:41:29 +03:00
}
2017-08-10 16:01:11 +03:00
}
2016-11-29 09:45:49 +03:00
}
2017-02-28 08:13:23 +03:00
2017-08-10 16:01:11 +03:00
let rotation = Double(recognizer.rotation)
recognizer.rotation = 0
2017-02-28 13:41:29 +03:00
2017-08-10 16:01:11 +03:00
recognizersMap[recognizer]?.forEach { node in
let event = RotateEvent(node: node, angle: rotation)
node.handleRotate(event)
}
if recognizer.state == .ended || recognizer.state == .cancelled {
recognizersMap.removeValue(forKey: recognizer)
}
}
// MARK: - Pinch
func handlePinch(_ recognizer: MPinchGestureRecognizer) {
if !self.node.shouldCheckForPinch() {
return
}
guard let renderer = renderer else {
return
}
if recognizer.state == .began {
let location = recognizer.location(in: self)
localContext { ctx in
guard let foundNode = renderer.findNodeAt(location: location, ctx: ctx) else {
return
2016-11-29 09:45:49 +03:00
}
2017-02-28 13:41:29 +03:00
2017-08-10 16:01:11 +03:00
if self.recognizersMap[recognizer] == nil {
self.recognizersMap[recognizer] = [Node]()
2017-02-28 13:41:29 +03:00
}
2017-08-10 16:01:11 +03:00
var parent: Node? = foundNode
while parent != .none {
if parent!.shouldCheckForPinch() {
self.recognizersMap[recognizer]?.append(parent!)
}
parent = nodesMap.parents(parent!).first
2017-02-28 13:41:29 +03:00
}
2017-08-10 16:01:11 +03:00
}
2016-11-29 09:45:49 +03:00
}
2017-08-10 16:01:11 +03:00
let scale = Double(recognizer.mScale)
recognizer.mScale = 1
2017-03-02 10:44:20 +03:00
2017-08-10 16:01:11 +03:00
recognizersMap[recognizer]?.forEach { node in
let event = PinchEvent(node: node, scale: scale)
node.handlePinch(event)
2017-03-02 10:44:20 +03:00
}
2017-08-10 16:01:11 +03:00
if recognizer.state == .ended || recognizer.state == .cancelled {
recognizersMap.removeValue(forKey: recognizer)
2017-03-02 10:44:20 +03:00
}
2017-08-10 16:01:11 +03:00
}
deinit {
nodesMap.remove(node)
}
2017-08-17 15:29:26 +03:00
// MARK: - MGestureRecognizerDelegate
2017-08-10 16:01:11 +03:00
public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldReceive touch: MTouch) -> Bool {
return true
}
public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: MGestureRecognizer) -> Bool {
return true
}
2016-09-20 15:35:55 +03:00
}