mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 05:05:23 +03:00
Merge pull request #671 from f3dm76/task/layer-animation-example
Add correct zoom-scroll handling
This commit is contained in:
commit
15027f0e53
@ -13,6 +13,9 @@ class AnimationsHierarchyViewController: UIViewController {
|
|||||||
|
|
||||||
@IBOutlet weak var animView: MacawView!
|
@IBOutlet weak var animView: MacawView!
|
||||||
|
|
||||||
|
var startCallbacks: [()->()] = []
|
||||||
|
var stopCallbacks: [()->()] = []
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
@ -20,6 +23,22 @@ class AnimationsHierarchyViewController: UIViewController {
|
|||||||
animView.zoom.enable()
|
animView.zoom.enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
startCallbacks.forEach {
|
||||||
|
$0()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
stopCallbacks.forEach {
|
||||||
|
$0()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createTree(height: Int) -> Node {
|
func createTree(height: Int) -> Node {
|
||||||
let rect = Rect(w: 10, h: 10)
|
let rect = Rect(w: 10, h: 10)
|
||||||
|
|
||||||
@ -52,7 +71,14 @@ class AnimationsHierarchyViewController: UIViewController {
|
|||||||
func createLeaf(childForm: Locus, xDelta: Double, yDelta: Double) -> Group {
|
func createLeaf(childForm: Locus, xDelta: Double, yDelta: Double) -> Group {
|
||||||
let inset = childForm.bounds().w / 2
|
let inset = childForm.bounds().w / 2
|
||||||
let leaf = Shape(form: childForm, fill: Color.teal, place: .move(dx: -inset, dy: -inset))
|
let leaf = Shape(form: childForm, fill: Color.teal, place: .move(dx: -inset, dy: -inset))
|
||||||
leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle().play()
|
|
||||||
|
let animation = leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle()
|
||||||
|
startCallbacks.append({
|
||||||
|
animation.play()
|
||||||
|
})
|
||||||
|
stopCallbacks.append({
|
||||||
|
animation.stop()
|
||||||
|
})
|
||||||
|
|
||||||
let leafGroup = [leaf].group(place: .move(dx: xDelta, dy: yDelta))
|
let leafGroup = [leaf].group(place: .move(dx: xDelta, dy: yDelta))
|
||||||
leaf.onTap { _ in
|
leaf.onTap { _ in
|
||||||
|
@ -24,11 +24,8 @@ class BasicAnimation: Animation {
|
|||||||
|
|
||||||
weak var node: Node? {
|
weak var node: Node? {
|
||||||
didSet {
|
didSet {
|
||||||
node?.animations.append(self)
|
if !(self is CombineAnimation || self is AnimationSequence || self is EmptyAnimation) {
|
||||||
if let group = node as? Group {
|
node?.animations.append(self)
|
||||||
for node in group.contents {
|
|
||||||
node.animations.append(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,6 +101,8 @@ class BasicAnimation: Animation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeFunc?()
|
removeFunc?()
|
||||||
|
node?.animations.removeAll { $0 === self }
|
||||||
|
nodeRenderer?.freeLayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func pause() {
|
override open func pause() {
|
||||||
@ -115,6 +114,8 @@ class BasicAnimation: Animation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeFunc?()
|
removeFunc?()
|
||||||
|
node?.animations.removeAll { $0 === self }
|
||||||
|
nodeRenderer?.freeLayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func state() -> AnimationState {
|
override func state() -> AnimationState {
|
||||||
|
@ -143,9 +143,9 @@ class AnimationProducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Sequence animation
|
// MARK: - Sequence animation
|
||||||
func addAnimationSequence(_ animationSequnce: Animation,
|
func addAnimationSequence(_ animationSequence: Animation,
|
||||||
_ context: AnimationContext) {
|
_ context: AnimationContext) {
|
||||||
guard let sequence = animationSequnce as? AnimationSequence else {
|
guard let sequence = animationSequence as? AnimationSequence else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ class AnimationProducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Stored animation
|
// MARK: - Stored animation
|
||||||
func addStoredAnimations(_ node: Node, _ view: MacawView) {
|
func addStoredAnimations(_ node: Node, _ view: DrawingView) {
|
||||||
addStoredAnimations(node, AnimationContext())
|
addStoredAnimations(node, AnimationContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import AppKit
|
|||||||
|
|
||||||
class AnimationUtils {
|
class AnimationUtils {
|
||||||
|
|
||||||
class func layerForNodeRenderer(_ renderer: NodeRenderer, _ context: AnimationContext, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
|
class func layerForNodeRenderer(_ renderer: NodeRenderer, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
|
||||||
|
|
||||||
let node = renderer.node
|
let node = renderer.node
|
||||||
if let cachedLayer = renderer.layer {
|
if let cachedLayer = renderer.layer {
|
||||||
|
@ -115,8 +115,8 @@ extension AnimationProducer {
|
|||||||
// MARK: - Combine animation
|
// MARK: - Combine animation
|
||||||
func addCombineAnimation(_ combineAnimation: Animation, _ context: AnimationContext) {
|
func addCombineAnimation(_ combineAnimation: Animation, _ context: AnimationContext) {
|
||||||
guard let combine = combineAnimation as? CombineAnimation,
|
guard let combine = combineAnimation as? CombineAnimation,
|
||||||
let renderer = combine.nodeRenderer,
|
let _ = combine.nodeRenderer,
|
||||||
let _ = renderer.view else {
|
let node = combine.node else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +176,7 @@ extension AnimationProducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
combine.removeFunc = {
|
combine.removeFunc = {
|
||||||
|
node.animations.removeAll { $0 === combine }
|
||||||
animations.forEach { animation in
|
animations.forEach { animation in
|
||||||
animation.removeFunc?()
|
animation.removeFunc?()
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
|
|||||||
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
||||||
|
|
||||||
let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false)
|
let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false)
|
||||||
|
|
||||||
// Creating proper animation
|
// Creating proper animation
|
||||||
let generatedAnimation = pathAnimation(
|
let generatedAnimation = pathAnimation(
|
||||||
@ -94,6 +94,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
|
|||||||
let animationId = animation.ID
|
let animationId = animation.ID
|
||||||
layer.add(generatedAnimation, forKey: animationId)
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
animation.removeFunc = { [weak layer] in
|
animation.removeFunc = { [weak layer] in
|
||||||
|
shape.animations.removeAll { $0 === animation }
|
||||||
layer?.removeAnimation(forKey: animationId)
|
layer?.removeAnimation(forKey: animationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,15 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
|
|||||||
node.opacityVar.value = opacityAnimation.getVFunc()(1.0)
|
node.opacityVar.value = opacityAnimation.getVFunc()(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CATransaction.begin()
|
||||||
|
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
|
||||||
|
renderer.layer?.animationLayer.opacity = Float(node.opacity)
|
||||||
|
CATransaction.commit()
|
||||||
|
|
||||||
|
if !animation.paused {
|
||||||
|
animation.removeFunc?()
|
||||||
|
}
|
||||||
|
|
||||||
renderer.freeLayer()
|
renderer.freeLayer()
|
||||||
|
|
||||||
if !animation.cycled &&
|
if !animation.cycled &&
|
||||||
@ -58,10 +67,11 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation)
|
let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation)
|
||||||
let animationId = animation.ID
|
let animationId = animation.ID
|
||||||
layer.add(generatedAnimation, forKey: animationId)
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
animation.removeFunc = { [weak layer] in
|
animation.removeFunc = { [weak layer] in
|
||||||
|
node.animations.removeAll { $0 === animation }
|
||||||
layer?.removeAnimation(forKey: animationId)
|
layer?.removeAnimation(forKey: animationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
|
|||||||
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
||||||
|
|
||||||
let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false)
|
let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false)
|
||||||
|
|
||||||
// Creating proper animation
|
// Creating proper animation
|
||||||
let generatedAnimation = generateShapeAnimation(context,
|
let generatedAnimation = generateShapeAnimation(context,
|
||||||
|
@ -22,7 +22,7 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
|
|||||||
let transactionsDisabled = CATransaction.disableActions()
|
let transactionsDisabled = CATransaction.disableActions()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
|
|
||||||
let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: true)
|
let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: true)
|
||||||
|
|
||||||
// Creating proper animation
|
// Creating proper animation
|
||||||
let generatedAnimation = transformAnimationByFunc(transformAnimation,
|
let generatedAnimation = transformAnimationByFunc(transformAnimation,
|
||||||
@ -54,6 +54,15 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
|
|||||||
node.placeVar.value = transformAnimation.getVFunc()(1.0)
|
node.placeVar.value = transformAnimation.getVFunc()(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CATransaction.begin()
|
||||||
|
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
|
||||||
|
renderer.layer?.animationLayer.transform = CATransform3DMakeAffineTransform(node.place.toCG())
|
||||||
|
CATransaction.commit()
|
||||||
|
|
||||||
|
if !animation.paused {
|
||||||
|
animation.removeFunc?()
|
||||||
|
}
|
||||||
|
|
||||||
renderer.freeLayer()
|
renderer.freeLayer()
|
||||||
|
|
||||||
if !animation.cycled &&
|
if !animation.cycled &&
|
||||||
@ -68,6 +77,7 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
|
|||||||
let animationId = animation.ID
|
let animationId = animation.ID
|
||||||
layer.add(generatedAnimation, forKey: animationId)
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
animation.removeFunc = { [weak layer] in
|
animation.removeFunc = { [weak layer] in
|
||||||
|
node.animations.removeAll { $0 === animation }
|
||||||
layer?.removeAnimation(forKey: animationId)
|
layer?.removeAnimation(forKey: animationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ public extension MacawView {
|
|||||||
y: -size.height / bounds.height
|
y: -size.height / bounds.height
|
||||||
)
|
)
|
||||||
|
|
||||||
context.cgContext = ctx
|
drawingView.context.cgContext = ctx
|
||||||
renderer?.render(in: ctx, force: false, opacity: node.opacity)
|
drawingView.renderer?.render(in: ctx, force: false, opacity: node.opacity)
|
||||||
|
|
||||||
ctx.endPDFPage()
|
ctx.endPDFPage()
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class GroupRenderer: NodeRenderer {
|
|||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
init(group: Group, view: MacawView?, parentRenderer: GroupRenderer? = nil) {
|
init(group: Group, view: DrawingView?, parentRenderer: GroupRenderer? = nil) {
|
||||||
self.group = group
|
self.group = group
|
||||||
super.init(node: group, view: view, parentRenderer: parentRenderer)
|
super.init(node: group, view: view, parentRenderer: parentRenderer)
|
||||||
updateRenderers()
|
updateRenderers()
|
||||||
|
@ -17,7 +17,7 @@ class ImageRenderer: NodeRenderer {
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
init(image: Image, view: MacawView?, parentRenderer: GroupRenderer? = nil) {
|
init(image: Image, view: DrawingView?, parentRenderer: GroupRenderer? = nil) {
|
||||||
self.image = image
|
self.image = image
|
||||||
super.init(node: image, view: view, parentRenderer: parentRenderer)
|
super.init(node: image, view: view, parentRenderer: parentRenderer)
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class CachedLayer {
|
|||||||
|
|
||||||
class NodeRenderer {
|
class NodeRenderer {
|
||||||
|
|
||||||
weak var view: MacawView?
|
weak var view: DrawingView?
|
||||||
var sceneLayer: CALayer? {
|
var sceneLayer: CALayer? {
|
||||||
return view?.mLayer
|
return view?.mLayer
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ class NodeRenderer {
|
|||||||
fatalError("Unsupported")
|
fatalError("Unsupported")
|
||||||
}
|
}
|
||||||
|
|
||||||
init(node: Node, view: MacawView?, parentRenderer: GroupRenderer? = nil) {
|
init(node: Node, view: DrawingView?, parentRenderer: GroupRenderer? = nil) {
|
||||||
self.view = view
|
self.view = view
|
||||||
self.parentRenderer = parentRenderer
|
self.parentRenderer = parentRenderer
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@ import UIKit
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
class RenderContext {
|
class RenderContext {
|
||||||
weak var view: MacawView?
|
weak var view: DrawingView?
|
||||||
var cgContext: CGContext?
|
var cgContext: CGContext?
|
||||||
|
|
||||||
init(view: MacawView?) {
|
init(view: DrawingView?) {
|
||||||
self.view = view
|
self.view = view
|
||||||
self.cgContext = nil
|
self.cgContext = nil
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class RenderUtils {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
class func createNodeRenderer(_ node: Node, view: MacawView?, parentRenderer: GroupRenderer? = nil) -> NodeRenderer {
|
class func createNodeRenderer(_ node: Node, view: DrawingView?, parentRenderer: GroupRenderer? = nil) -> NodeRenderer {
|
||||||
if let group = node as? Group {
|
if let group = node as? Group {
|
||||||
return GroupRenderer(group: group, view: view, parentRenderer: parentRenderer)
|
return GroupRenderer(group: group, view: view, parentRenderer: parentRenderer)
|
||||||
} else if let shape = node as? Shape {
|
} else if let shape = node as? Shape {
|
||||||
|
@ -10,7 +10,7 @@ class ShapeRenderer: NodeRenderer {
|
|||||||
|
|
||||||
var shape: Shape
|
var shape: Shape
|
||||||
|
|
||||||
init(shape: Shape, view: MacawView?, parentRenderer: GroupRenderer? = nil) {
|
init(shape: Shape, view: DrawingView?, parentRenderer: GroupRenderer? = nil) {
|
||||||
self.shape = shape
|
self.shape = shape
|
||||||
super.init(node: shape, view: view, parentRenderer: parentRenderer)
|
super.init(node: shape, view: view, parentRenderer: parentRenderer)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class TextRenderer: NodeRenderer {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
init(text: Text, view: MacawView?, parentRenderer: GroupRenderer? = nil) {
|
init(text: Text, view: DrawingView?, parentRenderer: GroupRenderer? = nil) {
|
||||||
self.text = text
|
self.text = text
|
||||||
super.init(node: text, view: view, parentRenderer: parentRenderer)
|
super.init(node: text, view: view, parentRenderer: parentRenderer)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,196 @@ import AppKit
|
|||||||
/// MacawView is a main class used to embed Macaw scene into your Cocoa UI.
|
/// 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.
|
/// You could create your own view extended from MacawView with predefined scene.
|
||||||
///
|
///
|
||||||
|
|
||||||
open class MacawView: MView, MGestureRecognizerDelegate {
|
open class MacawView: MView, MGestureRecognizerDelegate {
|
||||||
|
|
||||||
|
internal var drawingView = DrawingView()
|
||||||
|
|
||||||
|
public let zoom = MacawZoom()
|
||||||
|
|
||||||
|
open var node: Node {
|
||||||
|
get { return drawingView.node }
|
||||||
|
set { drawingView.node = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
open var contentLayout: ContentLayout {
|
||||||
|
get { return drawingView.contentLayout }
|
||||||
|
set { drawingView.contentLayout = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var contentMode: MViewContentMode {
|
||||||
|
get { return drawingView.contentMode }
|
||||||
|
set { drawingView.contentMode = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
open var place: Transform {
|
||||||
|
get { return drawingView.place }
|
||||||
|
}
|
||||||
|
|
||||||
|
open var placeVar: Variable<Transform> {
|
||||||
|
get { return drawingView.placeVar }
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var frame: CGRect {
|
||||||
|
didSet {
|
||||||
|
super.frame = frame
|
||||||
|
drawingView.frame = frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var intrinsicContentSize: CGSize {
|
||||||
|
get { return drawingView.intrinsicContentSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal var renderer: NodeRenderer? {
|
||||||
|
get { return drawingView.renderer }
|
||||||
|
set { drawingView.renderer = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(OSX)
|
||||||
|
open override var layer: CALayer? {
|
||||||
|
didSet {
|
||||||
|
guard self.layer != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
initializeView()
|
||||||
|
|
||||||
|
renderer = RenderUtils.createNodeRenderer(node, view: drawingView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@objc public convenience required init?(coder aDecoder: NSCoder) {
|
||||||
|
self.init(node: Group(), coder: aDecoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public init?(node: Node, coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
|
self.node = node
|
||||||
|
self.renderer = RenderUtils.createNodeRenderer(node, view: drawingView)
|
||||||
|
|
||||||
|
zoom.initialize(view: self, onChange: onZoomChange)
|
||||||
|
initializeView()
|
||||||
|
}
|
||||||
|
|
||||||
|
public convenience init(node: Node, frame: CGRect) {
|
||||||
|
self.init(frame: frame)
|
||||||
|
|
||||||
|
self.node = node
|
||||||
|
self.renderer = RenderUtils.createNodeRenderer(node, view: drawingView)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
zoom.initialize(view: self, onChange: onZoomChange)
|
||||||
|
initializeView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onZoomChange(t: Transform) {
|
||||||
|
if let viewLayer = drawingView.mLayer {
|
||||||
|
viewLayer.transform = CATransform3DMakeAffineTransform(t.toCG())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeView() {
|
||||||
|
|
||||||
|
if !self.subviews.contains(drawingView) {
|
||||||
|
if self.backgroundColor == nil {
|
||||||
|
self.backgroundColor = .white
|
||||||
|
}
|
||||||
|
self.addSubview(drawingView)
|
||||||
|
drawingView.backgroundColor = .clear
|
||||||
|
drawingView.initializeView()
|
||||||
|
|
||||||
|
drawingView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
drawingView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
|
||||||
|
drawingView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
|
||||||
|
drawingView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||||
|
drawingView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
self.clipsToBounds = true
|
||||||
|
drawingView.isUserInteractionEnabled = false
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
let tapRecognizer = MTapGestureRecognizer(target: drawingView, action: #selector(DrawingView.handleTap(recognizer:)))
|
||||||
|
let longTapRecognizer = MLongPressGestureRecognizer(target: drawingView, action: #selector(DrawingView.handleLongTap(recognizer:)))
|
||||||
|
let panRecognizer = MPanGestureRecognizer(target: drawingView, action: #selector(DrawingView.handlePan))
|
||||||
|
let rotationRecognizer = MRotationGestureRecognizer(target: drawingView, action: #selector(DrawingView.handleRotation))
|
||||||
|
let pinchRecognizer = MPinchGestureRecognizer(target: drawingView, action: #selector(DrawingView.handlePinch))
|
||||||
|
|
||||||
|
tapRecognizer.delegate = self
|
||||||
|
longTapRecognizer.delegate = self
|
||||||
|
panRecognizer.delegate = self
|
||||||
|
rotationRecognizer.delegate = self
|
||||||
|
pinchRecognizer.delegate = self
|
||||||
|
|
||||||
|
tapRecognizer.cancelsTouchesInView = false
|
||||||
|
longTapRecognizer.cancelsTouchesInView = false
|
||||||
|
panRecognizer.cancelsTouchesInView = false
|
||||||
|
rotationRecognizer.cancelsTouchesInView = false
|
||||||
|
pinchRecognizer.cancelsTouchesInView = false
|
||||||
|
|
||||||
|
self.removeGestureRecognizers()
|
||||||
|
self.addGestureRecognizer(tapRecognizer)
|
||||||
|
self.addGestureRecognizer(longTapRecognizer)
|
||||||
|
self.addGestureRecognizer(panRecognizer)
|
||||||
|
self.addGestureRecognizer(rotationRecognizer)
|
||||||
|
self.addGestureRecognizer(pinchRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func mTouchesBegan(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.mTouchesBegan(touches, with: event)
|
||||||
|
zoom.touchesBegan(touches)
|
||||||
|
|
||||||
|
drawingView.touchesBegan(touchPoints: convert(touches: touches))
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func mTouchesMoved(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.mTouchesMoved(touches, with: event)
|
||||||
|
zoom.touchesMoved(touches)
|
||||||
|
|
||||||
|
drawingView.touchesMoved(touchPoints: convert(touches: touches))
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func mTouchesEnded(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.mTouchesEnded(touches, with: event)
|
||||||
|
zoom.touchesEnded(touches)
|
||||||
|
|
||||||
|
drawingView.touchesEnded(touchPoints: convert(touches: touches))
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func mTouchesCancelled(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.mTouchesCancelled(touches, with: event)
|
||||||
|
zoom.touchesEnded(touches)
|
||||||
|
|
||||||
|
drawingView.touchesEnded(touchPoints: convert(touches: touches))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func convert(touches: Set<MTouch>) -> [MTouchEvent] {
|
||||||
|
return touches.map { touch -> MTouchEvent in
|
||||||
|
let location = touch.location(in: self).toMacaw()
|
||||||
|
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||||
|
return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - MGestureRecognizerDelegate
|
||||||
|
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldReceive touch: MTouch) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: MGestureRecognizer) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DrawingView: MView {
|
||||||
|
|
||||||
/// Scene root node
|
/// Scene root node
|
||||||
open var node: Node = Group() {
|
open var node: Node = Group() {
|
||||||
didSet {
|
didSet {
|
||||||
@ -42,8 +230,6 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public let zoom = MacawZoom()
|
|
||||||
|
|
||||||
public var place: Transform {
|
public var place: Transform {
|
||||||
return placeManager.placeVar.value
|
return placeManager.placeVar.value
|
||||||
}
|
}
|
||||||
@ -52,8 +238,6 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
return placeManager.placeVar
|
return placeManager.placeVar
|
||||||
}
|
}
|
||||||
|
|
||||||
private let placeManager = RootPlaceManager()
|
|
||||||
|
|
||||||
override open var frame: CGRect {
|
override open var frame: CGRect {
|
||||||
didSet {
|
didSet {
|
||||||
super.frame = frame
|
super.frame = frame
|
||||||
@ -68,6 +252,14 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override open var intrinsicContentSize: CGSize {
|
||||||
|
if let bounds = node.bounds {
|
||||||
|
return bounds.size().toCG()
|
||||||
|
} else {
|
||||||
|
return CGSize(width: MNoIntrinsicMetric(), height: MNoIntrinsicMetric())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override open func didMoveToSuperview() {
|
override open func didMoveToSuperview() {
|
||||||
super.didMoveToSuperview()
|
super.didMoveToSuperview()
|
||||||
|
|
||||||
@ -78,14 +270,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
animationProducer.addStoredAnimations(node, self)
|
animationProducer.addStoredAnimations(node, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
override open var intrinsicContentSize: CGSize {
|
private let placeManager = RootPlaceManager()
|
||||||
if let bounds = node.bounds {
|
|
||||||
return bounds.size().toCG()
|
|
||||||
} else {
|
|
||||||
return CGSize(width: MNoIntrinsicMetric(), height: MNoIntrinsicMetric())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let layoutHelper = LayoutHelper()
|
private let layoutHelper = LayoutHelper()
|
||||||
|
|
||||||
var touchesMap = [MTouchEvent: [NodePath]]()
|
var touchesMap = [MTouchEvent: [NodePath]]()
|
||||||
@ -98,82 +283,17 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
var toRender = true
|
var toRender = true
|
||||||
var frameSetFirstTime = false
|
var frameSetFirstTime = false
|
||||||
|
|
||||||
#if os(OSX)
|
func initializeView() {
|
||||||
open override var layer: CALayer? {
|
self.contentLayout = .none
|
||||||
didSet {
|
self.context = RenderContext(view: self)
|
||||||
guard self.layer != nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
initializeView()
|
|
||||||
|
|
||||||
self.renderer = RenderUtils.createNodeRenderer(node, view: self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
@objc public init?(node: Node, coder aDecoder: NSCoder) {
|
@objc public required init?(coder aDecoder: NSCoder) {
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
zoom.initialize(view: self, onChange: onZoomChange)
|
|
||||||
|
|
||||||
initializeView()
|
|
||||||
|
|
||||||
self.node = node
|
|
||||||
self.renderer = RenderUtils.createNodeRenderer(node, view: self)
|
|
||||||
backgroundColor = .white
|
|
||||||
}
|
|
||||||
|
|
||||||
public convenience init(node: Node, frame: CGRect) {
|
|
||||||
self.init(frame: frame)
|
|
||||||
|
|
||||||
self.node = node
|
|
||||||
self.renderer = RenderUtils.createNodeRenderer(node, view: self)
|
|
||||||
backgroundColor = .white
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
zoom.initialize(view: self, onChange: onZoomChange)
|
|
||||||
|
|
||||||
initializeView()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc public convenience required init?(coder aDecoder: NSCoder) {
|
|
||||||
self.init(node: Group(), coder: aDecoder)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func onZoomChange(t: Transform) {
|
|
||||||
placeManager.setZoom(place: t)
|
|
||||||
self.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initializeView() {
|
|
||||||
self.contentLayout = .none
|
|
||||||
self.context = RenderContext(view: self)
|
|
||||||
|
|
||||||
let tapRecognizer = MTapGestureRecognizer(target: self, action: #selector(MacawView.handleTap))
|
|
||||||
let longTapRecognizer = MLongPressGestureRecognizer(target: self, action: #selector(MacawView.handleLongTap(recognizer:)))
|
|
||||||
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
|
|
||||||
longTapRecognizer.delegate = self
|
|
||||||
panRecognizer.delegate = self
|
|
||||||
rotationRecognizer.delegate = self
|
|
||||||
pinchRecognizer.delegate = self
|
|
||||||
|
|
||||||
tapRecognizer.cancelsTouchesInView = false
|
|
||||||
longTapRecognizer.cancelsTouchesInView = false
|
|
||||||
panRecognizer.cancelsTouchesInView = false
|
|
||||||
rotationRecognizer.cancelsTouchesInView = false
|
|
||||||
pinchRecognizer.cancelsTouchesInView = false
|
|
||||||
|
|
||||||
self.removeGestureRecognizers()
|
|
||||||
self.addGestureRecognizer(tapRecognizer)
|
|
||||||
self.addGestureRecognizer(longTapRecognizer)
|
|
||||||
self.addGestureRecognizer(panRecognizer)
|
|
||||||
self.addGestureRecognizer(rotationRecognizer)
|
|
||||||
self.addGestureRecognizer(pinchRecognizer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func layoutSubviews() {
|
open override func layoutSubviews() {
|
||||||
@ -194,12 +314,12 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
guard let renderer = renderer else {
|
guard let renderer = renderer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderer.calculateZPositionRecursively()
|
|
||||||
|
|
||||||
// TODO: actually we should track all changes
|
// TODO: actually we should track all changes
|
||||||
placeManager.setLayout(place: layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()))
|
placeManager.setLayout(place: layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()))
|
||||||
|
|
||||||
ctx.concatenate(self.place.toCG())
|
ctx.concatenate(self.place.toCG())
|
||||||
|
|
||||||
|
renderer.calculateZPositionRecursively()
|
||||||
renderer.render(in: ctx, force: false, opacity: node.opacity)
|
renderer.render(in: ctx, force: false, opacity: node.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,10 +349,8 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Touches
|
// MARK: - Touches
|
||||||
override func mTouchesBegan(_ touches: Set<MTouch>, with event: MEvent?) {
|
func touchesBegan(touchPoints: [MTouchEvent]) {
|
||||||
zoom.touchesBegan(touches)
|
|
||||||
|
|
||||||
let touchPoints = convert(touches: touches)
|
|
||||||
if !self.node.shouldCheckForPressed() &&
|
if !self.node.shouldCheckForPressed() &&
|
||||||
!self.node.shouldCheckForMoved() &&
|
!self.node.shouldCheckForMoved() &&
|
||||||
!self.node.shouldCheckForReleased () {
|
!self.node.shouldCheckForReleased () {
|
||||||
@ -280,8 +398,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mTouchesMoved(_ touches: Set<MTouch>, with event: MEvent?) {
|
func touchesMoved(touchPoints: [MTouchEvent]) {
|
||||||
zoom.touchesMoved(touches)
|
|
||||||
if !self.node.shouldCheckForMoved() {
|
if !self.node.shouldCheckForMoved() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -290,7 +407,6 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let touchPoints = convert(touches: touches)
|
|
||||||
touchesOfNode.keys.forEach { currentNode in
|
touchesOfNode.keys.forEach { currentNode in
|
||||||
guard let initialTouches = touchesOfNode[currentNode] else {
|
guard let initialTouches = touchesOfNode[currentNode] else {
|
||||||
return
|
return
|
||||||
@ -324,30 +440,12 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mTouchesCancelled(_ touches: Set<MTouch>, with event: MEvent?) {
|
func touchesEnded(touchPoints: [MTouchEvent]) {
|
||||||
touchesEnded(touches: touches)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func mTouchesEnded(_ touches: Set<MTouch>, with event: MEvent?) {
|
|
||||||
touchesEnded(touches: touches)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func convert(touches: Set<MTouch>) -> [MTouchEvent] {
|
|
||||||
return 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func touchesEnded(touches: Set<MTouch>) {
|
|
||||||
zoom.touchesEnded(touches)
|
|
||||||
guard let _ = renderer else {
|
guard let _ = renderer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let invertedViewPlace = self.place.invert()
|
let invertedViewPlace = self.place.invert()
|
||||||
let touchPoints = convert(touches: touches)
|
|
||||||
for touch in touchPoints {
|
for touch in touchPoints {
|
||||||
|
|
||||||
touchesMap[touch]?.forEach { nodePath in
|
touchesMap[touch]?.forEach { nodePath in
|
||||||
@ -566,16 +664,6 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
recognizersMap.removeValue(forKey: recognizer)
|
recognizersMap.removeValue(forKey: recognizer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - MGestureRecognizerDelegate
|
|
||||||
|
|
||||||
public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldReceive touch: MTouch) -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: MGestureRecognizer) -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LayoutHelper {
|
class LayoutHelper {
|
||||||
|
@ -16,7 +16,7 @@ import AppKit
|
|||||||
|
|
||||||
open class MacawZoom {
|
open class MacawZoom {
|
||||||
|
|
||||||
private var view: MView!
|
private var view: MacawView!
|
||||||
private var onChange: ((Transform) -> Void)!
|
private var onChange: ((Transform) -> Void)!
|
||||||
private var touches = [TouchData]()
|
private var touches = [TouchData]()
|
||||||
private var zoomData = ZoomData()
|
private var zoomData = ZoomData()
|
||||||
@ -50,7 +50,7 @@ open class MacawZoom {
|
|||||||
onChange(zoomData.transform())
|
onChange(zoomData.transform())
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialize(view: MView, onChange: @escaping ((Transform) -> Void)) {
|
func initialize(view: MacawView, onChange: @escaping ((Transform) -> Void)) {
|
||||||
self.view = view
|
self.view = view
|
||||||
self.onChange = onChange
|
self.onChange = onChange
|
||||||
}
|
}
|
||||||
@ -81,6 +81,9 @@ open class MacawZoom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func getNewZoom() -> ZoomData {
|
private func getNewZoom() -> ZoomData {
|
||||||
|
if !trackMove && !trackScale && !trackRotate {
|
||||||
|
return zoomData
|
||||||
|
}
|
||||||
if touches.isEmpty || (touches.count == 1 && !trackMove) {
|
if touches.isEmpty || (touches.count == 1 && !trackMove) {
|
||||||
return zoomData
|
return zoomData
|
||||||
}
|
}
|
||||||
@ -93,15 +96,7 @@ open class MacawZoom {
|
|||||||
let e2 = touches[1].current(in: view)
|
let e2 = touches[1].current(in: view)
|
||||||
let scale = trackScale ? e1.distance(to: e2) / s1.distance(to: s2) : 1
|
let scale = trackScale ? e1.distance(to: e2) / s1.distance(to: s2) : 1
|
||||||
let a = trackRotate ? (e1 - e2).angle() - (s1 - s2).angle() : 0
|
let a = trackRotate ? (e1 - e2).angle() - (s1 - s2).angle() : 0
|
||||||
var offset = Size.zero
|
return ZoomData(offset: .zero, scale: scale, angle: a).combine(with: zoomData)
|
||||||
if trackMove {
|
|
||||||
let sina = sin(a)
|
|
||||||
let cosa = cos(a)
|
|
||||||
let w = e1.x - scale * (s1.x * cosa - s1.y * sina)
|
|
||||||
let h = e1.y - scale * (s1.x * sina + s1.y * cosa)
|
|
||||||
offset = Size(w: w, h: h)
|
|
||||||
}
|
|
||||||
return ZoomData(offset: offset, scale: scale, angle: a).combine(with: zoomData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -143,7 +138,7 @@ fileprivate class TouchData {
|
|||||||
let touch: MTouch
|
let touch: MTouch
|
||||||
let point: Point
|
let point: Point
|
||||||
|
|
||||||
convenience init(touch: MTouch, in view: MView) {
|
convenience init(touch: MTouch, in view: MacawView) {
|
||||||
self.init(touch: touch, point: touch.location(in: view).toMacaw())
|
self.init(touch: touch, point: touch.location(in: view).toMacaw())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +147,7 @@ fileprivate class TouchData {
|
|||||||
self.point = point
|
self.point = point
|
||||||
}
|
}
|
||||||
|
|
||||||
func current(in view: MView) -> Point {
|
func current(in view: MacawView) -> Point {
|
||||||
return touch.location(in: view).toMacaw()
|
return touch.location(in: view).toMacaw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,9 +17,6 @@ class ShapeLayer: CAShapeLayer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let renderContext = RenderContext(view: .none)
|
|
||||||
renderContext.cgContext = ctx
|
|
||||||
|
|
||||||
renderer?.directRender(in: ctx, force: isForceRenderingEnabled)
|
renderer?.directRender(in: ctx, force: isForceRenderingEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user