mirror of
https://github.com/exyte/Macaw.git
synced 2024-08-15 16:10:39 +03:00
WIP #561: Built-in zoom support
This commit is contained in:
parent
9a1abccbb6
commit
84602c48a0
@ -258,8 +258,9 @@
|
||||
TargetAttributes = {
|
||||
B02E75EC1C16104900D1971D = {
|
||||
CreatedOnToolsVersion = 7.1.1;
|
||||
DevelopmentTeam = FZXCM5CJ7P;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Manual;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -502,7 +503,8 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = FZXCM5CJ7P;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@ -521,13 +523,15 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = FZXCM5CJ7P;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.exyte.Example.Example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
|
@ -234,6 +234,8 @@
|
||||
5852891620B29D67003E51D1 /* 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 */; };
|
||||
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 */; };
|
||||
58944BDB20AC8A9A00657640 /* clip.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B064C1F9738EF00787A35 /* clip.svg */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1295,6 +1298,7 @@
|
||||
57F108731F502A3600DC365B /* Touchable.swift */,
|
||||
57E5E1501E3B393900D1CB28 /* MacawView.swift */,
|
||||
57E5E1521E3B393900D1CB28 /* ShapeLayer.swift */,
|
||||
5876C63122572859000B31B6 /* MacawZoom.swift */,
|
||||
);
|
||||
path = views;
|
||||
sourceTree = "<group>";
|
||||
@ -2082,6 +2086,7 @@
|
||||
57614B3A1F83D15600875933 /* AnimationProducer.swift in Sources */,
|
||||
585288F620AD96A2003E51D1 /* ContentLayout.swift in Sources */,
|
||||
57614B3C1F83D15600875933 /* ShapeInterpolation.swift in Sources */,
|
||||
5876C63322572859000B31B6 /* MacawZoom.swift in Sources */,
|
||||
57614B3D1F83D15600875933 /* Graphics_iOS.swift in Sources */,
|
||||
57614BDB1F8739EE00875933 /* MacawView+PDF.swift in Sources */,
|
||||
57614B411F83D15600875933 /* Text.swift in Sources */,
|
||||
@ -2222,6 +2227,7 @@
|
||||
57A27BD51E44C5840057BD3A /* ShapeInterpolation.swift in Sources */,
|
||||
A718CD471F45C28700966E06 /* Graphics_iOS.swift in Sources */,
|
||||
57614BDA1F8739EE00875933 /* MacawView+PDF.swift in Sources */,
|
||||
5876C63222572859000B31B6 /* MacawZoom.swift in Sources */,
|
||||
57E5E1A21E3B393900D1CB28 /* Text.swift in Sources */,
|
||||
57F1087C1F53CA7E00DC365B /* MDisplayLink_iOS.swift in Sources */,
|
||||
57E5E1A61E3B393900D1CB28 /* RenderContext.swift in Sources */,
|
||||
|
@ -384,8 +384,8 @@ class AnimationContext {
|
||||
|
||||
func getLayoutTransform(_ renderer: NodeRenderer?) -> Transform {
|
||||
if rootTransform == nil {
|
||||
if let view = renderer?.view, let node = view.renderer?.node() {
|
||||
rootTransform = LayoutHelper.calcTransform(node, view.contentLayout, view.bounds.size.toMacaw())
|
||||
if let view = renderer?.view {
|
||||
rootTransform = view.place
|
||||
}
|
||||
}
|
||||
return rootTransform ?? Transform.identity
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Darwin
|
||||
import Foundation
|
||||
|
||||
open class Arc: Locus {
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
open class Point: Locus {
|
||||
|
||||
public let x: Double
|
||||
@ -11,24 +13,35 @@ open class Point: Locus {
|
||||
}
|
||||
|
||||
override open func bounds() -> Rect {
|
||||
return Rect(
|
||||
x: x,
|
||||
y: y,
|
||||
w: 0.0,
|
||||
h: 0.0)
|
||||
return Rect(x: x, y: y, w: 0.0, h: 0.0)
|
||||
}
|
||||
|
||||
open func add(_ point: Point) -> Point {
|
||||
return Point(
|
||||
x: self.x + point.x,
|
||||
y: self.y + point.y)
|
||||
return Point( x: x + point.x, y: y + point.y)
|
||||
}
|
||||
|
||||
open func rect(size: Size) -> Rect {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
public let w: Double
|
||||
@ -13,11 +15,25 @@ open class Size {
|
||||
open func rect(at point: Point = Point.origin) -> Rect {
|
||||
return Rect(point: point, size: self)
|
||||
}
|
||||
|
||||
open func angle() -> Double {
|
||||
return atan2(h, w)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Size {
|
||||
|
||||
public static func == (lhs: Size, rhs: Size) -> Bool {
|
||||
return lhs.w == rhs.w
|
||||
&& lhs.h == rhs.h
|
||||
return lhs.w == rhs.w && 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)
|
||||
}
|
||||
|
||||
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? {
|
||||
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,
|
||||
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)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
///
|
||||
/// 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.
|
||||
@ -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 {
|
||||
didSet {
|
||||
super.frame = frame
|
||||
@ -106,6 +119,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
|
||||
@objc public init?(node: Node, coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
zoom.initialize(view: self, onChange: onZoomChange)
|
||||
|
||||
initializeView()
|
||||
|
||||
@ -128,6 +142,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
zoom.initialize(view: self, onChange: onZoomChange)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
@ -136,6 +151,11 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
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)
|
||||
@ -191,7 +211,11 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@ -210,7 +234,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
defer {
|
||||
ctx.restoreGState()
|
||||
}
|
||||
let transform = layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw())
|
||||
let transform = place.toCG()
|
||||
ctx.concatenate(transform)
|
||||
let loc = location.applying(transform.inverted())
|
||||
return renderer.findNodeAt(parentNodePath: NodePath(node: Node(), location: loc), ctx: ctx)
|
||||
@ -225,10 +249,29 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
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
|
||||
|
||||
override func mTouchesBegan(_ touches: [MTouchEvent]) {
|
||||
|
||||
if !self.node.shouldCheckForPressed() &&
|
||||
!self.node.shouldCheckForMoved() &&
|
||||
!self.node.shouldCheckForReleased () {
|
||||
@ -547,9 +590,9 @@ class LayoutHelper {
|
||||
|
||||
private var prevSize: Size?
|
||||
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)
|
||||
let node = nodeRenderer.node()
|
||||
var rect = node?.bounds
|
||||
@ -566,9 +609,9 @@ class LayoutHelper {
|
||||
if let transform = prevTransform {
|
||||
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 {
|
||||
@ -624,9 +667,39 @@ class LayoutHelper {
|
||||
prevTransform = nil
|
||||
}
|
||||
|
||||
private func setTransform(transform: CGAffineTransform) -> CGAffineTransform {
|
||||
private func setTransform(transform: Transform) -> Transform {
|
||||
prevTransform = 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