mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 05:05:23 +03:00
WIP #561: Built-in zoom support
This commit is contained in:
parent
9a1abccbb6
commit
84602c48a0
@ -258,8 +258,9 @@
|
|||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
B02E75EC1C16104900D1971D = {
|
B02E75EC1C16104900D1971D = {
|
||||||
CreatedOnToolsVersion = 7.1.1;
|
CreatedOnToolsVersion = 7.1.1;
|
||||||
|
DevelopmentTeam = FZXCM5CJ7P;
|
||||||
LastSwiftMigration = 1020;
|
LastSwiftMigration = 1020;
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -502,7 +503,8 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
DEVELOPMENT_TEAM = "";
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = FZXCM5CJ7P;
|
||||||
INFOPLIST_FILE = Example/Info.plist;
|
INFOPLIST_FILE = Example/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
@ -521,13 +523,15 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
DEVELOPMENT_TEAM = "";
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = FZXCM5CJ7P;
|
||||||
INFOPLIST_FILE = Example/Info.plist;
|
INFOPLIST_FILE = Example/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.exyte.Example.Example;
|
PRODUCT_BUNDLE_IDENTIFIER = com.exyte.Example.Example;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
@ -234,6 +234,8 @@
|
|||||||
5852891620B29D67003E51D1 /* TransformedLocus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5852891520B29D67003E51D1 /* TransformedLocus.swift */; };
|
5852891620B29D67003E51D1 /* TransformedLocus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5852891520B29D67003E51D1 /* TransformedLocus.swift */; };
|
||||||
5852891720B29D67003E51D1 /* TransformedLocus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5852891520B29D67003E51D1 /* TransformedLocus.swift */; };
|
5852891720B29D67003E51D1 /* TransformedLocus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5852891520B29D67003E51D1 /* TransformedLocus.swift */; };
|
||||||
5874CCB720DA8A860090DBD5 /* ColorMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5874CCB620DA8A860090DBD5 /* ColorMatrix.swift */; };
|
5874CCB720DA8A860090DBD5 /* ColorMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5874CCB620DA8A860090DBD5 /* ColorMatrix.swift */; };
|
||||||
|
5876C63222572859000B31B6 /* MacawZoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5876C63122572859000B31B6 /* MacawZoom.swift */; };
|
||||||
|
5876C63322572859000B31B6 /* MacawZoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5876C63122572859000B31B6 /* MacawZoom.swift */; };
|
||||||
58944BDA20AC8A9A00657640 /* logo_base64.txt in Resources */ = {isa = PBXBuildFile; fileRef = 57B7A4E01EE70DA5009D78D7 /* logo_base64.txt */; };
|
58944BDA20AC8A9A00657640 /* logo_base64.txt in Resources */ = {isa = PBXBuildFile; fileRef = 57B7A4E01EE70DA5009D78D7 /* logo_base64.txt */; };
|
||||||
58944BDB20AC8A9A00657640 /* clip.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B064C1F9738EF00787A35 /* clip.svg */; };
|
58944BDB20AC8A9A00657640 /* clip.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B064C1F9738EF00787A35 /* clip.svg */; };
|
||||||
58B0523920E10E7100D45008 /* ColorMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5874CCB620DA8A860090DBD5 /* ColorMatrix.swift */; };
|
58B0523920E10E7100D45008 /* ColorMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5874CCB620DA8A860090DBD5 /* ColorMatrix.swift */; };
|
||||||
@ -676,6 +678,7 @@
|
|||||||
585288F320AD96A2003E51D1 /* ContentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentLayout.swift; sourceTree = "<group>"; };
|
585288F320AD96A2003E51D1 /* ContentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentLayout.swift; sourceTree = "<group>"; };
|
||||||
5852891520B29D67003E51D1 /* TransformedLocus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformedLocus.swift; sourceTree = "<group>"; };
|
5852891520B29D67003E51D1 /* TransformedLocus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformedLocus.swift; sourceTree = "<group>"; };
|
||||||
5874CCB620DA8A860090DBD5 /* ColorMatrix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorMatrix.swift; sourceTree = "<group>"; };
|
5874CCB620DA8A860090DBD5 /* ColorMatrix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorMatrix.swift; sourceTree = "<group>"; };
|
||||||
|
5876C63122572859000B31B6 /* MacawZoom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacawZoom.swift; sourceTree = "<group>"; };
|
||||||
5B1A8C7520A15F7300E5FFAE /* SVGNodeLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGNodeLayout.swift; sourceTree = "<group>"; };
|
5B1A8C7520A15F7300E5FFAE /* SVGNodeLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGNodeLayout.swift; sourceTree = "<group>"; };
|
||||||
5B1AE18420B6A669007EECCB /* text-align-01-b-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "text-align-01-b-manual.svg"; sourceTree = "<group>"; };
|
5B1AE18420B6A669007EECCB /* text-align-01-b-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "text-align-01-b-manual.svg"; sourceTree = "<group>"; };
|
||||||
5B1AE18520B6A669007EECCB /* paths-data-06-t-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "paths-data-06-t-manual.reference"; sourceTree = "<group>"; };
|
5B1AE18520B6A669007EECCB /* paths-data-06-t-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "paths-data-06-t-manual.reference"; sourceTree = "<group>"; };
|
||||||
@ -1295,6 +1298,7 @@
|
|||||||
57F108731F502A3600DC365B /* Touchable.swift */,
|
57F108731F502A3600DC365B /* Touchable.swift */,
|
||||||
57E5E1501E3B393900D1CB28 /* MacawView.swift */,
|
57E5E1501E3B393900D1CB28 /* MacawView.swift */,
|
||||||
57E5E1521E3B393900D1CB28 /* ShapeLayer.swift */,
|
57E5E1521E3B393900D1CB28 /* ShapeLayer.swift */,
|
||||||
|
5876C63122572859000B31B6 /* MacawZoom.swift */,
|
||||||
);
|
);
|
||||||
path = views;
|
path = views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2082,6 +2086,7 @@
|
|||||||
57614B3A1F83D15600875933 /* AnimationProducer.swift in Sources */,
|
57614B3A1F83D15600875933 /* AnimationProducer.swift in Sources */,
|
||||||
585288F620AD96A2003E51D1 /* ContentLayout.swift in Sources */,
|
585288F620AD96A2003E51D1 /* ContentLayout.swift in Sources */,
|
||||||
57614B3C1F83D15600875933 /* ShapeInterpolation.swift in Sources */,
|
57614B3C1F83D15600875933 /* ShapeInterpolation.swift in Sources */,
|
||||||
|
5876C63322572859000B31B6 /* MacawZoom.swift in Sources */,
|
||||||
57614B3D1F83D15600875933 /* Graphics_iOS.swift in Sources */,
|
57614B3D1F83D15600875933 /* Graphics_iOS.swift in Sources */,
|
||||||
57614BDB1F8739EE00875933 /* MacawView+PDF.swift in Sources */,
|
57614BDB1F8739EE00875933 /* MacawView+PDF.swift in Sources */,
|
||||||
57614B411F83D15600875933 /* Text.swift in Sources */,
|
57614B411F83D15600875933 /* Text.swift in Sources */,
|
||||||
@ -2222,6 +2227,7 @@
|
|||||||
57A27BD51E44C5840057BD3A /* ShapeInterpolation.swift in Sources */,
|
57A27BD51E44C5840057BD3A /* ShapeInterpolation.swift in Sources */,
|
||||||
A718CD471F45C28700966E06 /* Graphics_iOS.swift in Sources */,
|
A718CD471F45C28700966E06 /* Graphics_iOS.swift in Sources */,
|
||||||
57614BDA1F8739EE00875933 /* MacawView+PDF.swift in Sources */,
|
57614BDA1F8739EE00875933 /* MacawView+PDF.swift in Sources */,
|
||||||
|
5876C63222572859000B31B6 /* MacawZoom.swift in Sources */,
|
||||||
57E5E1A21E3B393900D1CB28 /* Text.swift in Sources */,
|
57E5E1A21E3B393900D1CB28 /* Text.swift in Sources */,
|
||||||
57F1087C1F53CA7E00DC365B /* MDisplayLink_iOS.swift in Sources */,
|
57F1087C1F53CA7E00DC365B /* MDisplayLink_iOS.swift in Sources */,
|
||||||
57E5E1A61E3B393900D1CB28 /* RenderContext.swift in Sources */,
|
57E5E1A61E3B393900D1CB28 /* RenderContext.swift in Sources */,
|
||||||
|
@ -384,8 +384,8 @@ class AnimationContext {
|
|||||||
|
|
||||||
func getLayoutTransform(_ renderer: NodeRenderer?) -> Transform {
|
func getLayoutTransform(_ renderer: NodeRenderer?) -> Transform {
|
||||||
if rootTransform == nil {
|
if rootTransform == nil {
|
||||||
if let view = renderer?.view, let node = view.renderer?.node() {
|
if let view = renderer?.view {
|
||||||
rootTransform = LayoutHelper.calcTransform(node, view.contentLayout, view.bounds.size.toMacaw())
|
rootTransform = view.place
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rootTransform ?? Transform.identity
|
return rootTransform ?? Transform.identity
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Darwin
|
import Foundation
|
||||||
|
|
||||||
open class Arc: Locus {
|
open class Arc: Locus {
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
open class Point: Locus {
|
open class Point: Locus {
|
||||||
|
|
||||||
public let x: Double
|
public let x: Double
|
||||||
@ -11,24 +13,35 @@ open class Point: Locus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override open func bounds() -> Rect {
|
override open func bounds() -> Rect {
|
||||||
return Rect(
|
return Rect(x: x, y: y, w: 0.0, h: 0.0)
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
w: 0.0,
|
|
||||||
h: 0.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open func add(_ point: Point) -> Point {
|
open func add(_ point: Point) -> Point {
|
||||||
return Point(
|
return Point( x: x + point.x, y: y + point.y)
|
||||||
x: self.x + point.x,
|
|
||||||
y: self.y + point.y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open func rect(size: Size) -> Rect {
|
open func rect(size: Size) -> Rect {
|
||||||
return Rect(point: self, size: size)
|
return Rect(point: self, size: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func distance(to point: Point) -> Double {
|
||||||
|
let dx = point.x - x
|
||||||
|
let dy = point.y - y
|
||||||
|
return sqrt(dx * dx + dy * dy)
|
||||||
|
}
|
||||||
|
|
||||||
override open func toPath() -> Path {
|
override open func toPath() -> Path {
|
||||||
return MoveTo(x: x, y: y).lineTo(x: x, y: y).build()
|
return MoveTo(x: x, y: y).lineTo(x: x, y: y).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Point: Equatable {
|
||||||
|
public static func == (lhs: Point, rhs: Point) -> Bool {
|
||||||
|
return lhs.x == rhs.x
|
||||||
|
&& lhs.y == rhs.y
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func - (lhs: Point, rhs: Point) -> Size {
|
||||||
|
return Size(w: lhs.x - rhs.x, h: lhs.y - rhs.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
open class Size {
|
open class Size {
|
||||||
|
|
||||||
public let w: Double
|
public let w: Double
|
||||||
@ -13,11 +15,25 @@ open class Size {
|
|||||||
open func rect(at point: Point = Point.origin) -> Rect {
|
open func rect(at point: Point = Point.origin) -> Rect {
|
||||||
return Rect(point: point, size: self)
|
return Rect(point: point, size: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func angle() -> Double {
|
||||||
|
return atan2(h, w)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Size {
|
extension Size {
|
||||||
|
|
||||||
public static func == (lhs: Size, rhs: Size) -> Bool {
|
public static func == (lhs: Size, rhs: Size) -> Bool {
|
||||||
return lhs.w == rhs.w
|
return lhs.w == rhs.w && lhs.h == rhs.h
|
||||||
&& lhs.h == rhs.h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func + (lhs: Size, rhs: Size) -> Size {
|
||||||
|
return Size(w: lhs.w + rhs.w, h: lhs.h + rhs.h)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func - (lhs: Size, rhs: Size) -> Size {
|
||||||
|
return Size(w: lhs.w - rhs.w, h: lhs.h - rhs.h)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -75,12 +75,19 @@ public final class Transform {
|
|||||||
return Transform(m11: nm11, m12: nm12, m21: nm21, m22: nm22, dx: ndx, dy: ndy)
|
return Transform(m11: nm11, m12: nm12, m21: nm21, m22: nm22, dx: ndx, dy: ndy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func apply(to: Point) -> Point {
|
||||||
|
let x2 = m11 * to.x + m12 * to.x + dx
|
||||||
|
let y2 = m21 * to.y + m22 * to.y + dy
|
||||||
|
return Point(x: x2, y: y2)
|
||||||
|
}
|
||||||
|
|
||||||
public func invert() -> Transform? {
|
public func invert() -> Transform? {
|
||||||
let det = self.m11 * self.m22 - self.m12 * self.m21
|
let det = self.m11 * self.m22 - self.m12 * self.m21
|
||||||
if det == 0 {
|
if det == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return Transform(m11: m22 / det, m12: -m12 / det, m21: -m21 / det, m22: m11 / det,
|
return Transform(m11: m22 / det, m12: -m12 / det,
|
||||||
|
m21: -m21 / det, m22: m11 / det,
|
||||||
dx: (m21 * dy - m22 * dx) / det,
|
dx: (m21 * dy - m22 * dx) / det,
|
||||||
dy: (m12 * dx - m11 * dy) / det)
|
dy: (m12 * dx - m11 * dy) / det)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import UIKit
|
|||||||
#elseif os(OSX)
|
#elseif os(OSX)
|
||||||
import AppKit
|
import AppKit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
///
|
///
|
||||||
/// 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.
|
||||||
@ -43,6 +44,18 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public let zoom = MacawZoom()
|
||||||
|
|
||||||
|
public var place: Transform {
|
||||||
|
return placeManager.placeVar.value
|
||||||
|
}
|
||||||
|
|
||||||
|
public var placeVar: Variable<Transform> {
|
||||||
|
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
|
||||||
@ -106,6 +119,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
|
|
||||||
@objc public init?(node: Node, coder aDecoder: NSCoder) {
|
@objc public init?(node: Node, coder aDecoder: NSCoder) {
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
zoom.initialize(view: self, onChange: onZoomChange)
|
||||||
|
|
||||||
initializeView()
|
initializeView()
|
||||||
|
|
||||||
@ -128,6 +142,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
|
|
||||||
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()
|
initializeView()
|
||||||
}
|
}
|
||||||
@ -136,6 +151,11 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
self.init(node: Group(), coder: aDecoder)
|
self.init(node: Group(), coder: aDecoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func onZoomChange(t: Transform) {
|
||||||
|
placeManager.setZoom(place: t)
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
func initializeView() {
|
func initializeView() {
|
||||||
self.contentLayout = .none
|
self.contentLayout = .none
|
||||||
self.context = RenderContext(view: self)
|
self.context = RenderContext(view: self)
|
||||||
@ -191,7 +211,11 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderer.calculateZPositionRecursively()
|
renderer.calculateZPositionRecursively()
|
||||||
ctx.concatenate(layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()))
|
|
||||||
|
// TODO: actually we should track all changes
|
||||||
|
placeManager.setLayout(place: layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()))
|
||||||
|
|
||||||
|
ctx.concatenate(self.place.toCG())
|
||||||
renderer.render(in: ctx, force: false, opacity: node.opacity)
|
renderer.render(in: ctx, force: false, opacity: node.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +234,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
defer {
|
defer {
|
||||||
ctx.restoreGState()
|
ctx.restoreGState()
|
||||||
}
|
}
|
||||||
let transform = layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw())
|
let transform = place.toCG()
|
||||||
ctx.concatenate(transform)
|
ctx.concatenate(transform)
|
||||||
let loc = location.applying(transform.inverted())
|
let loc = location.applying(transform.inverted())
|
||||||
return renderer.findNodeAt(parentNodePath: NodePath(node: Node(), location: loc), ctx: ctx)
|
return renderer.findNodeAt(parentNodePath: NodePath(node: Node(), location: loc), ctx: ctx)
|
||||||
@ -225,10 +249,29 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open override func touchesBegan(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.touchesBegan(touches, with: event)
|
||||||
|
zoom.touchesBegan(touches)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func touchesMoved(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.touchesMoved(touches, with: event)
|
||||||
|
zoom.touchesMoved(touches)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func touchesEnded(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
zoom.touchesEnded(touches)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func touchesCancelled(_ touches: Set<MTouch>, with event: MEvent?) {
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
zoom.touchesEnded(touches)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Touches
|
// MARK: - Touches
|
||||||
|
|
||||||
override func mTouchesBegan(_ touches: [MTouchEvent]) {
|
override func mTouchesBegan(_ touches: [MTouchEvent]) {
|
||||||
|
|
||||||
if !self.node.shouldCheckForPressed() &&
|
if !self.node.shouldCheckForPressed() &&
|
||||||
!self.node.shouldCheckForMoved() &&
|
!self.node.shouldCheckForMoved() &&
|
||||||
!self.node.shouldCheckForReleased () {
|
!self.node.shouldCheckForReleased () {
|
||||||
@ -547,9 +590,9 @@ class LayoutHelper {
|
|||||||
|
|
||||||
private var prevSize: Size?
|
private var prevSize: Size?
|
||||||
private var prevRect: Rect?
|
private var prevRect: Rect?
|
||||||
private var prevTransform: CGAffineTransform?
|
private var prevTransform: Transform?
|
||||||
|
|
||||||
public func getTransform(_ nodeRenderer: NodeRenderer, _ layout: ContentLayout, _ size: Size) -> CGAffineTransform {
|
public func getTransform(_ nodeRenderer: NodeRenderer, _ layout: ContentLayout, _ size: Size) -> Transform {
|
||||||
setSize(size: size)
|
setSize(size: size)
|
||||||
let node = nodeRenderer.node()
|
let node = nodeRenderer.node()
|
||||||
var rect = node?.bounds
|
var rect = node?.bounds
|
||||||
@ -566,9 +609,9 @@ class LayoutHelper {
|
|||||||
if let transform = prevTransform {
|
if let transform = prevTransform {
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
return setTransform(transform: layout.layout(rect: prevRect!, into: size).toCG())
|
return setTransform(transform: layout.layout(rect: rect, into: size))
|
||||||
}
|
}
|
||||||
return CGAffineTransform.identity
|
return Transform.identity
|
||||||
}
|
}
|
||||||
|
|
||||||
public class func calcTransform(_ node: Node, _ layout: ContentLayout, _ size: Size) -> Transform {
|
public class func calcTransform(_ node: Node, _ layout: ContentLayout, _ size: Size) -> Transform {
|
||||||
@ -624,9 +667,39 @@ class LayoutHelper {
|
|||||||
prevTransform = nil
|
prevTransform = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setTransform(transform: CGAffineTransform) -> CGAffineTransform {
|
private func setTransform(transform: Transform) -> Transform {
|
||||||
prevTransform = transform
|
prevTransform = transform
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RootPlaceManager {
|
||||||
|
|
||||||
|
var placeVar = Variable(Transform.identity)
|
||||||
|
private var places: [Transform] = [.identity, .identity]
|
||||||
|
|
||||||
|
func setLayout(place: Transform) {
|
||||||
|
if places[1] !== place {
|
||||||
|
places[1] = place
|
||||||
|
placeVar.value = recalc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setZoom(place: Transform) {
|
||||||
|
if places[0] !== place {
|
||||||
|
places[0] = place
|
||||||
|
placeVar.value = recalc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func recalc() -> Transform {
|
||||||
|
if places[0] === Transform.identity {
|
||||||
|
return places[1]
|
||||||
|
} else if places[1] === Transform.identity {
|
||||||
|
return places[0]
|
||||||
|
}
|
||||||
|
return places[0].concat(with: places[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
162
Source/views/MacawZoom.swift
Normal file
162
Source/views/MacawZoom.swift
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
//
|
||||||
|
// MacawZoom.swift
|
||||||
|
// Macaw
|
||||||
|
//
|
||||||
|
// Created by Yuri Strot on 4/5/19.
|
||||||
|
// Copyright © 2019 Exyte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
import UIKit
|
||||||
|
#elseif os(OSX)
|
||||||
|
import AppKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
open class MacawZoom {
|
||||||
|
|
||||||
|
private var view: MView!
|
||||||
|
private var onChange: ((Transform) -> Void)!
|
||||||
|
private var touches = [TouchData]()
|
||||||
|
private var zoomData = ZoomData()
|
||||||
|
|
||||||
|
private var trackMove = false
|
||||||
|
private var trackScale = false
|
||||||
|
private var trackRotate = false
|
||||||
|
|
||||||
|
open func enable(move: Bool = true, scale: Bool = true, rotate: Bool = false) {
|
||||||
|
trackMove = move
|
||||||
|
trackScale = scale
|
||||||
|
trackRotate = rotate
|
||||||
|
if scale || rotate {
|
||||||
|
view.isMultipleTouchEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func disable() {
|
||||||
|
trackMove = false
|
||||||
|
trackScale = false
|
||||||
|
trackRotate = false
|
||||||
|
}
|
||||||
|
|
||||||
|
open func set(offset: Size? = nil, scale: Double? = nil, angle: Double? = nil) {
|
||||||
|
let o = offset ?? zoomData.offset
|
||||||
|
let s = scale ?? zoomData.scale
|
||||||
|
let a = angle ?? zoomData.angle
|
||||||
|
zoomData = ZoomData(offset: o, scale: s, angle: a)
|
||||||
|
onChange(zoomData.transform())
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialize(view: MView, onChange: @escaping ((Transform) -> Void)) {
|
||||||
|
self.view = view
|
||||||
|
self.onChange = onChange
|
||||||
|
}
|
||||||
|
|
||||||
|
func touchesBegan(_ touches: Set<MTouch>) {
|
||||||
|
zoomData = getNewZoom()
|
||||||
|
self.touches = self.touches.map { TouchData(touch: $0.touch, in: view) }
|
||||||
|
self.touches.append(contentsOf: touches.map { TouchData(touch: $0, in: view) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func touchesMoved(_ touches: Set<MTouch>) {
|
||||||
|
let zoom = cleanTouches() ?? getNewZoom()
|
||||||
|
onChange(zoom.transform())
|
||||||
|
}
|
||||||
|
|
||||||
|
func touchesEnded(_ touches: Set<MTouch>) {
|
||||||
|
cleanTouches()
|
||||||
|
if let touch = touches.first {
|
||||||
|
if touches.count == 1 && touch.tapCount == 2 && touch.timestamp + 0.3 >= CACurrentMediaTime() {
|
||||||
|
set(offset: .zero, scale: 1, angle: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult private func cleanTouches() -> ZoomData? {
|
||||||
|
let newTouches = touches.filter { $0.touch.phase.rawValue < MTouch.Phase.ended.rawValue }
|
||||||
|
if newTouches.count != touches.count {
|
||||||
|
zoomData = getNewZoom()
|
||||||
|
touches = newTouches.map { TouchData(touch: $0.touch, in: view) }
|
||||||
|
return zoomData
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getNewZoom() -> ZoomData {
|
||||||
|
if touches.count == 0 || (touches.count == 1 && !trackMove) {
|
||||||
|
return zoomData
|
||||||
|
}
|
||||||
|
let s1 = touches[0].point
|
||||||
|
let e1 = touches[0].current(in: view)
|
||||||
|
if touches.count == 1 {
|
||||||
|
return zoomData.move(delta: e1 - s1)
|
||||||
|
}
|
||||||
|
let s2 = touches[1].point
|
||||||
|
let e2 = touches[1].current(in: view)
|
||||||
|
let scale = trackScale ? e1.distance(to: e2) / s1.distance(to: s2) : 1
|
||||||
|
let a = trackRotate ? (e1 - e2).angle() - (s1 - s2).angle() : 0
|
||||||
|
var offset = Size.zero
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class ZoomData {
|
||||||
|
|
||||||
|
let offset: Size
|
||||||
|
let scale: Double
|
||||||
|
let angle: Double
|
||||||
|
|
||||||
|
init(offset: Size = Size.zero, scale: Double = 1, angle: Double = 0) {
|
||||||
|
self.offset = offset
|
||||||
|
self.scale = scale
|
||||||
|
self.angle = angle
|
||||||
|
}
|
||||||
|
|
||||||
|
func transform() -> Transform {
|
||||||
|
return Transform.move(dx: offset.w, dy: offset.h).scale(sx: scale, sy: scale).rotate(angle: angle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func move(delta: Size) -> ZoomData {
|
||||||
|
return ZoomData(offset: offset + delta, scale: scale, angle: angle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func combine(with: ZoomData) -> ZoomData {
|
||||||
|
let sina = sin(angle)
|
||||||
|
let cosa = cos(angle)
|
||||||
|
let w = offset.w + scale * (cosa * with.offset.w - sina * with.offset.h)
|
||||||
|
let h = offset.h + scale * (sina * with.offset.w + cosa * with.offset.h)
|
||||||
|
let s = scale * with.scale
|
||||||
|
let a = angle + with.angle
|
||||||
|
return ZoomData(offset: Size(w: w, h: h), scale: s, angle: a)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class TouchData {
|
||||||
|
|
||||||
|
let touch: MTouch
|
||||||
|
let point: Point
|
||||||
|
|
||||||
|
convenience init(touch: MTouch, in view: MView) {
|
||||||
|
self.init(touch: touch, point: touch.location(in: view).toMacaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
init(touch: MTouch, point: Point) {
|
||||||
|
self.touch = touch
|
||||||
|
self.point = point
|
||||||
|
}
|
||||||
|
|
||||||
|
func current(in view: MView) -> Point {
|
||||||
|
return touch.location(in: view).toMacaw()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user