diff --git a/extras/Studio.framer/index.html b/extras/Studio.framer/index.html
index 2d82eb5d6..0df9f916b 100644
--- a/extras/Studio.framer/index.html
+++ b/extras/Studio.framer/index.html
@@ -15,7 +15,6 @@
var iOS = /iPad|iPhone|iPod/.test(navigator.platform)
if (iOS) { viewport += ", shrink-to-fit=no" }
document.write("")
- if (window.ontouchstart == undefined) { window.ontouchstart = null }
})()
diff --git a/framer/Extras/Extras.coffee b/framer/Extras/Extras.coffee
index e2eb405e0..d006ae92b 100644
--- a/framer/Extras/Extras.coffee
+++ b/framer/Extras/Extras.coffee
@@ -1,3 +1,4 @@
# exports.Hints = require "./Hints"
+exports.TouchEmulator = require "./TouchEmulator"
exports.MobileScrollFix = require "./MobileScrollFix"
-exports.OmitNew = require "./OmitNew"
\ No newline at end of file
+exports.OmitNew = require "./OmitNew"
diff --git a/framer/Extras/TouchEmulator.coffee b/framer/Extras/TouchEmulator.coffee
new file mode 100644
index 000000000..d8c7f4694
--- /dev/null
+++ b/framer/Extras/TouchEmulator.coffee
@@ -0,0 +1,206 @@
+Utils = require "../Utils"
+{BaseClass} = require "../BaseClass"
+
+createTouch = (event, identifier, offset={x:0, y:0}) ->
+ return touch =
+ identifier: identifier
+ target: event.target
+ pageX: event.pageX + offset.x
+ pageY: event.pageY + offset.y
+ clientX: event.clientX + offset.x
+ clientY: event.clientY + offset.y
+ screenX: event.screenX + offset.x
+ screenY: event.screenY + offset.y
+
+dispatchTouchEvent = (type, target, event, offset) ->
+
+ target ?= event.target
+
+ touchEvent = document.createEvent("MouseEvent")
+ touchEvent.initMouseEvent(type, true, true, window,
+ event.detail, event.screenX, event.screenY,
+ event.clientX, event.clientY,
+ event.ctrlKey, event.shiftKey, event.altKey, event.metaKey,
+ event.button, event.relatedTarget)
+
+ touches = []
+ touches.push(createTouch(event, 1))
+ touches.push(createTouch(event, 2, offset)) if offset
+
+ touchEvent.touches = touchEvent.changedTouches = touchEvent.targetTouches = touches
+
+ target.dispatchEvent(touchEvent)
+
+cancelEvent = (event) ->
+ event.preventDefault()
+ event.stopPropagation()
+
+class TouchEmulator extends BaseClass
+
+ constructor: ->
+
+ if not @isHammerTouchSupported()
+ throw new Error "Touch emulation for hammer is not supported"
+
+ @touchPointerImage = "framer/images/cursor@2x.png"
+ @touchPointerImageActive = "framer/images/cursor-active@2x.png"
+ @touchPointerImageSize = 64
+ @touchPointerInitialOffset = {x:0, y:0}
+
+ @keyPinchCode = 18 # Alt
+ @keyPanCode = 91 # Command
+
+ @context = new Framer.Context name:"TouchEmulator"
+ @context._element.style.zIndex = 10000
+ @wrap = @context.domEventManager.wrap
+
+ @wrap(document).addEventListener("mousedown", @mousedown, true)
+ @wrap(document).addEventListener("mousemove", @mousemovePosition, true)
+ @wrap(document).addEventListener("keydown", @keydown, true)
+
+ @isMouseDown = false
+ @isPinchKeyDown = false
+ @isPanKeyDown = false
+
+ touchPointerInitialOffset = @touchPointerInitialOffset
+
+ @context.run =>
+ @touchPointLayer = new Layer
+ width: @touchPointerImageSize
+ height: @touchPointerImageSize
+ image: @touchPointerImage
+ opacity: 0
+
+ showTouchCursor: ->
+ @touchPointLayer.animateStop()
+ @touchPointLayer.midX = @point.x
+ @touchPointLayer.midY = @point.y
+ @touchPointLayer.scale = 1.8
+ @touchPointLayer.animate
+ properties:
+ opacity: 1
+ scale: 1
+ # midX: @point.x + @touchPointerInitialOffset.x
+ # midY: @point.y + @touchPointerInitialOffset.y
+ time: 0.1
+ curve: "ease-out"
+
+ hideTouchCursor: ->
+ @touchPointLayer.animateStop()
+ @touchPointLayer.animate
+ properties:
+ opacity: 0
+ scale: 1.2
+ time: 0.08
+
+ isHammerTouchSupported: ->
+ window.ontouchstart is null
+
+ keydown: (event) =>
+
+ if event.keyCode is @keyPinchCode
+ @isPinchKeyDown = true
+ @startPoint = @centerPoint = null
+ @showTouchCursor()
+ @touchPointLayer.midX = @point.x
+ @touchPointLayer.midY = @point.y
+ @wrap(document).addEventListener("keyup", @keyup, true)
+ @wrap(document).addEventListener("mousemove", @mousemove, true)
+
+ if event.keyCode is @keyPanCode
+ @isPanKeyDown = true
+ cancelEvent(event)
+
+ keyup: (event) =>
+
+ if event.keyCode is @keyPinchCode
+ cancelEvent(event)
+ @isPinchKeyDown = false
+ @hideTouchCursor()
+
+ @wrap(document).removeEventListener("mousemove", @mousemove, true)
+
+ if event.keyCode is @keyPanCode
+ cancelEvent(event)
+ @centerPoint = Utils.pointCenter(@touchPoint, @point)
+ @isPanKeyDown = false
+
+
+ mousedown: (event) =>
+
+ cancelEvent(event)
+
+ @isMouseDown = true
+ @target = event.target
+
+ @wrap(document).addEventListener("mousemove", @mousemove, true)
+ @wrap(document).addEventListener("mouseup", @mouseup, true)
+
+ if @isPinchKeyDown
+ dispatchTouchEvent("touchstart", @target, event, @touchPointDelta)
+ else
+ dispatchTouchEvent("touchstart", @target, event)
+
+ @touchPointLayer.image = @touchPointerImageActive
+
+ mousemovePosition: (event) =>
+ @point =
+ x: event.pageX
+ y: event.pageY
+
+ mousemove: (event) =>
+
+ cancelEvent(event)
+
+ @startPoint ?=
+ x: event.pageX
+ y: event.pageY
+
+ @centerPoint ?= @startPoint
+
+ if @isPinchKeyDown and not @isPanKeyDown
+ @touchPoint = Utils.pointAdd(@touchPointerInitialOffset, @pinchPoint(@point, @centerPoint))
+ @touchPointDelta = Utils.pointSubtract(@point, @touchPoint)
+
+ if @isPinchKeyDown and @isPanKeyDown
+ @touchPoint = @panPoint(@point, @touchPointDelta)
+
+ if @isPinchKeyDown or @isPanKeyDown
+ @touchPointLayer.visible = true
+ @touchPointLayer.midX = @touchPoint.x
+ @touchPointLayer.midY = @touchPoint.y
+
+ if @isPinchKeyDown or @isPanKeyDown
+ dispatchTouchEvent("touchmove", @target, event, @touchPointDelta)
+ else
+ dispatchTouchEvent("touchmove", @target, event)
+
+ mouseup: (event) =>
+
+ @isMouseDown = false
+
+ cancelEvent(event)
+
+ @wrap(document).removeEventListener("mousemove", @mousemove, true)
+ @wrap(document).removeEventListener("mouseup", @mouseup, true)
+
+ if @isPinchKeyDown or @isPanKeyDown
+ dispatchTouchEvent("touchend", @target, event, @touchPointDelta)
+ else
+ dispatchTouchEvent("touchend", @target, event)
+
+ @touchPointLayer.image = @touchPointerImage
+ @hideTouchCursor()
+
+ pinchPoint: (point, centerPoint) ->
+ return Utils.pointSubtract(centerPoint,
+ Utils.pointSubtract(point, centerPoint))
+
+ panPoint: (point, offsetPoint) ->
+ return Utils.pointSubtract(point, offsetPoint)
+
+touchEmulator = null
+
+exports.enable = ->
+ return if Utils.isTouch()
+ touchEmulator ?= new TouchEmulator()
\ No newline at end of file
diff --git a/framer/Framer.coffee b/framer/Framer.coffee
index 59c916b7a..e7292977e 100644
--- a/framer/Framer.coffee
+++ b/framer/Framer.coffee
@@ -1,5 +1,9 @@
{_} = require "./Underscore"
+# Before we do anything else, we need to patch touch events
+if window.ontouchstart is undefined
+ window.ontouchstart = null
+
Framer = {}
# Root level modules
@@ -59,4 +63,5 @@ Framer.DefaultContext = new Framer.Context(name:"Default")
Framer.CurrentContext = Framer.DefaultContext
# Fix for mobile scrolling
-Framer.Extras.MobileScrollFix.enable() if Utils.isMobile()
\ No newline at end of file
+Framer.Extras.MobileScrollFix.enable() if Utils.isMobile()
+Framer.Extras.TouchEmulator.enable()
\ No newline at end of file
diff --git a/framer/GestureManager.coffee b/framer/GestureManager.coffee
index 4f940a2cf..59abdb258 100644
--- a/framer/GestureManager.coffee
+++ b/framer/GestureManager.coffee
@@ -8,7 +8,6 @@ Utils = require "./Utils"
class exports.GestureManager extends EventEmitter
constructor: (@layer) ->
- @_manager = Hammer(@layer._element)
once: (eventName, listener) =>
super(eventName, listener)
@@ -29,6 +28,8 @@ class exports.GestureManager extends EventEmitter
throw new Error("No event name defined") unless eventName
throw new Error("No listener defined") unless listener
+ @_manager = Hammer(@layer._element)
+
# Make sure we have a hammer instance and layer listeners enabled
@layer.ignoreEvents = false
@@ -53,7 +54,13 @@ class exports.GestureManager extends EventEmitter
@_manager.on(eventName, listener._actual)
_removeListener: (eventName, listener) ->
- @_manager.off(eventName, listener._actual)
+
+ # If this is the last listener we detroy the hammer element
+ if @listenerEvents().length is 1 and @listenerEvents()[0] is eventName
+ @_manager.destroy()
+ else
+ listener = listener._actual if listener._actual
+ @_manager.off(eventName, listener)
destroy: ->
@_manager.destroy()
@@ -152,51 +159,14 @@ class exports.GestureManager extends EventEmitter
if tap = @_manager.get(Gestures.Tap)
existingRecognizers.push(tap)
- return existingRecognizers
-
- onPan: (cb) -> @on(Gestures.Pan, cb)
- onPanStart: (cb) -> @on(Gestures.PanStart, cb)
- onPanMove: (cb) -> @on(Gestures.PanMove, cb)
- onPanEnd: (cb) -> @on(Gestures.PanEnd, cb)
- onPanCancel: (cb) -> @on(Gestures.PanCancel, cb)
- onPanLeft: (cb) -> @on(Gestures.PanLeft, cb)
- onPanRight: (cb) -> @on(Gestures.PanRight, cb)
- onPanUp: (cb) -> @on(Gestures.PanUp, cb)
- onPanDown: (cb) -> @on(Gestures.PanDown, cb)
-
- onPinch: (cb) -> @on(Gestures.Pinch, cb)
- onPinchStart: (cb) -> @on(Gestures.PinchStart, cb)
- onPinchMove: (cb) -> @on(Gestures.PinchMove, cb)
- onPinchEnd: (cb) -> @on(Gestures.PinchEnd, cb)
- onPinchCancel: (cb) -> @on(Gestures.PinchCancel, cb)
- onPinchIn: (cb) -> @on(Gestures.PinchIn, cb)
- onPinchOut: (cb) -> @on(Gestures.PinchOut, cb)
-
- onPress: (cb) -> @on(Gestures.Press, cb)
- onPressUp: (cb) -> @on(Gestures.PressUp, cb)
-
- onRotate: (cb) -> @on(Gestures.Rotate, cb)
- onRotateStart: (cb) -> @on(Gestures.RotateStart, cb)
- onRotateMove: (cb) -> @on(Gestures.RotateMove, cb)
- onRotateEnd: (cb) -> @on(Gestures.RotateEnd, cb)
- onRotateCancel: (cb) -> @on(Gestures.RotateCancel, cb)
-
- onSwipe: (cb) -> @on(Gestures.Swipe, cb)
- onSwipeLeft: (cb) -> @on(Gestures.SwipeLeft, cb)
- onSwipeRight: (cb) -> @on(Gestures.SwipeRight, cb)
- onSwipeUp: (cb) -> @on(Gestures.SwipeUp, cb)
- onSwipeDown: (cb) -> @on(Gestures.SwipeDown, cb)
-
- onTap: (cb) -> @on(Gestures.Tap, cb)
- onSingleTap: (cb) -> @on(Gestures.SingleTap, cb)
- onDoubleTap: (cb) -> @on(Gestures.DoubleTap, cb)
+ return existingRecognizers
##############################################################
# PATCH HAMMER
# This is a nasty monkey patch to get Hammer to use the DOMEventManager
-# We're not going to use this for now, but we can if things become slow.
+# TODO: it would be better if it would use the layer context then current
getWindowForElement = (element) ->
doc = element.ownerDocument or element
@@ -207,11 +177,13 @@ splitStr = (str) ->
addEventListeners = (target, types, handler) ->
splitStr(types).map (type) ->
+ #console.log("hammer.addEventListener", type)
Framer.CurrentContext.domEventManager.wrap(target)
.addEventListener(type, handler, false)
removeEventListeners = (target, types, handler) ->
splitStr(types).map (type) ->
+ #console.log("hammer.removeEventListener", type)
Framer.CurrentContext.domEventManager.wrap(target)
.removeEventListener(type, handler, false)
diff --git a/framer/Gestures.coffee b/framer/Gestures.coffee
index 7e4bfd6d2..94d4b33ce 100644
--- a/framer/Gestures.coffee
+++ b/framer/Gestures.coffee
@@ -1,5 +1,6 @@
# Gesture events
Gestures = {}
+Gestures._prefix = "gesture:"
# Pan
Gestures.Pan = "pan" # This event includes all the other Pan events
@@ -44,4 +45,9 @@ Gestures.Tap = "tap"
Gestures.SingleTap = "singletap"
Gestures.DoubleTap = "doubletap"
+# To determine gesture events, we prefix the value with rotate
+for k, v of Gestures
+ continue if k is "_prefix"
+ Gestures[k] = "#{Gestures._prefix}#{v}"
+
exports.Gestures = Gestures
\ No newline at end of file
diff --git a/framer/Layer.coffee b/framer/Layer.coffee
index 179d2051e..de09b92e8 100644
--- a/framer/Layer.coffee
+++ b/framer/Layer.coffee
@@ -13,8 +13,8 @@ Utils = require "./Utils"
{LayerStyle} = require "./LayerStyle"
{LayerStates} = require "./LayerStates"
{LayerDraggable} = require "./LayerDraggable"
-{LayerRotatable} = require "./LayerRotatable"
{LayerPinchable} = require "./LayerPinchable"
+{Gestures} = require "./Gestures"
{GestureManager} = require "./GestureManager"
NoCacheDateKey = Date.now()
@@ -567,7 +567,6 @@ class exports.Layer extends BaseClass
@_element.parentNode?.removeChild @_element
@removeAllListeners()
- @gestures.removeAllListeners()
@_context.removeLayer(@)
@_context.emit("layer:destroy", @)
@@ -888,7 +887,7 @@ class exports.Layer extends BaseClass
get: -> @_states ?= new LayerStates @
#############################################################################
- ## Draggable, Rotatable, Pinchable
+ ## Draggable, Pinchable
@define "draggable",
importable: false
@@ -896,12 +895,6 @@ class exports.Layer extends BaseClass
get: -> @_draggable ?= new LayerDraggable(@)
set: (value) -> @draggable.enabled = value if _.isBoolean(value)
- @define "rotatable",
- importable: false
- exportable: false
- get: -> @_rotatable ?= new LayerRotatable(@)
- set: (value) -> @rotatable.enabled = value if _.isBoolean(value)
-
@define "pinchable",
importable: false
exportable: false
@@ -973,6 +966,16 @@ class exports.Layer extends BaseClass
_addListener: (eventName, listener) ->
+ # Make sure we stop ignoring events once we add a user event listener
+ if not _.startsWith(eventName, "change:")
+ @ignoreEvents = false
+
+ # If this is a gesture event, pass it on to the gesture manager
+ if _.startsWith(eventName, Gestures._prefix)
+ @_gestureManager ?= new GestureManager(@)
+ @_gestureManager.on(eventName, listener)
+ return
+
# If this is a dom event, we want the actual dom node to let us know
# when it gets triggered, so we can emit the event through the system.
if Utils.domValidEvent(@_element, eventName)
@@ -980,12 +983,14 @@ class exports.Layer extends BaseClass
@_domEventManager.addEventListener eventName, (event) =>
@emit(eventName, event)
- # Make sure we stop ignoring events once we add a user event listener
- if not _.startsWith eventName, "change:"
- @ignoreEvents = false
-
_removeListener: (eventName, listener) ->
+ # If this is a gesture event, pass it on to the gesture manager
+ if _.startsWith(eventName, Gestures._prefix)
+ @_gestureManager ?= new GestureManager(@)
+ @_gestureManager.off(eventName, listener)
+ return
+
# Do cleanup for dom events if this is the last one of it's type.
# We are assuming we're the only ones adding dom events to the manager.
if not @listeners(eventName).length
@@ -999,14 +1004,6 @@ class exports.Layer extends BaseClass
on: @::addListener
off: @::removeListener
- ##############################################################
- ## EVENTS
-
- @define "gestures",
- get: ->
- @_gestures ?= new GestureManager(@)
- return @_gestures
-
##############################################################
## EVENT HELPERS
@@ -1046,6 +1043,16 @@ class exports.Layer extends BaseClass
onDragAnimationDidEnd: (cb) -> @on(Events.DragAnimationDidEnd, cb)
onDirectionLockDidStart: (cb) -> @on(Events.DirectionLockDidStart, cb)
+ onPinchStart: (cb) -> @on(Events.PinchStart, cb)
+ onPinchEnd: (cb) -> @on(Events.PinchEnd, cb)
+ onPinch: (cb) -> @on(Events.Pinch, cb)
+ onRotateStart: (cb) -> @on(Events.RotateStart, cb)
+ onRotate: (cb) -> @on(Events.Rotate, cb)
+ onRotateEnd: (cb) -> @on(Events.RotateEnd, cb)
+ onScaleStart: (cb) -> @on(Events.ScaleStart, cb)
+ onScale: (cb) -> @on(Events.Scale, cb)
+ onScaleEnd: (cb) -> @on(Events.ScaleEnd, cb)
+
##############################################################
## DESCRIPTOR
diff --git a/framer/LayerPinchable.coffee b/framer/LayerPinchable.coffee
index 9eb10557b..4d13c98e8 100644
--- a/framer/LayerPinchable.coffee
+++ b/framer/LayerPinchable.coffee
@@ -1,29 +1,111 @@
+Utils = require "./Utils"
+
{BaseClass} = require "./BaseClass"
{Events} = require "./Events"
{Gestures} = require "./Gestures"
-Events.PinchStart = Gestures.PinchStart
-Events.PinchEnd = Gestures.PinchEnd
-Events.Pinch = Gestures.Pinch
+Events.PinchStart = "pinchstart"
+Events.PinchEnd = "pinchstart"
+Events.Pinch = "pinchstart"
+Events.RotateStart = "rotatestart"
+Events.Rotate = "rotate"
+Events.RotateEnd = "rotateend"
+Events.ScaleStart = "scalestart"
+Events.Scale = "scale"
+Events.ScaleEnd = "scaleend"
class exports.LayerPinchable extends BaseClass
@define "enabled", @simpleProperty("enabled", true)
+ @define "threshold", @simpleProperty("threshold", 64)
+ @define "setOrigin", @simpleProperty("setOrigin", true)
+
+ @define "scale", @simpleProperty("scale", true)
+ @define "scaleIncrements", @simpleProperty("scaleIncrements", 0)
+ @define "scaleMin", @simpleProperty("scaleMin", 0)
+ @define "scaleMax", @simpleProperty("scaleMax", Number.MAX_VALUE)
+ @define "scaleFactor", @simpleProperty("scaleFactor", 1)
+
+ @define "rotate", @simpleProperty("rotate", true)
+ @define "rotateIncrements", @simpleProperty("rotateIncrements", 0)
+ @define "rotateMin", @simpleProperty("rotateMin", 0)
+ @define "rotateMax", @simpleProperty("rotateMax", 0)
+ @define "rotateFactor", @simpleProperty("rotateFactor", 1)
constructor: (@layer) ->
super
- @layer.gestures.on(Gestures.PinchStart, @_pinch)
- @layer.gestures.on(Gestures.Pinch, @_pinch)
- @layer.gestures.on(Gestures.PinchEnd, @_pinchEnd)
- _pinchStart: (event) =>
+ @_attach()
+
+ _attach: ->
+ @layer.on(Gestures.PinchStart, @_pinch)
+ @layer.on(Gestures.Pinch, @_pinch)
+ @layer.on(Gestures.PinchEnd, @_pinchEnd)
+
+ _reset: ->
@_scaleStart = null
+ @_rotationStart = null
+ @_rotationOffset = null
+
+ _pinchStart: (event) =>
+ @_reset()
@emit(Events.PinchStart, event)
+ @emit(Events.ScaleStart, event) if @scale
+ @emit(Events.RotateStart, event) if @rotate
_pinch: (event) =>
- @_scaleStart ?= @layer.scale
- @layer.scale = event.scale * @_scaleStart
- @emit(Events.Pinch, event)
+
+ return unless event.pointers.length is 2
+ return unless @enabled
+
+ pointA =
+ x: event.pointers[0].pageX
+ y: event.pointers[0].pageY
+
+ pointB =
+ x: event.pointers[1].pageX
+ y: event.pointers[1].pageY
+
+ return unless Utils.pointTotal(Utils.pointAbs(Utils.pointSubtract(pointA, pointB))) > @threshold
+
+ # TODO
+ # if @setOrigin
+
+ if @scale
+ @_scaleStart ?= @layer.scale
+ scale = event.scale * @_scaleStart
+ scale = scale * @scaleFactor
+ scale = Utils.clamp(scale, @scaleMin, @scaleMax) if (@scaleMin and @scaleMax)
+ scale = Utils.nearestIncrement(scale, @scaleIncrements) if @scaleIncrements
+ @layer.scale = scale
+ @emit(Events.Scale, event)
+
+ if @rotate
+ @_rotationStart ?= @layer.rotation
+ @_rotationOffset ?= event.rotation
+ rotation = event.rotation - @_rotationOffset + @_rotationStart
+ rotation = rotation * @rotateFactor
+ rotation = Utils.clamp(rotation, @rotateMin, @rotateMax) if (@rotateMin and @rotateMax)
+ rotation = Utils.nearestIncrement(rotation, @rotateIncrements) if @rotateIncrements
+ @layer.rotation = rotation
+ @emit(Events.Rotate, event)
_pinchEnd: (event) =>
- @emit(Events.PinchEnd, event)
\ No newline at end of file
+ @_reset()
+ @emit(Events.PinchEnd, event)
+ @emit(Events.ScaleEnd, event) if @scale
+ @emit(Events.RotateEnd, event) if @rotate
+
+ emit: (eventName, event) ->
+ @layer.emit(eventName, event, @)
+ super eventName, event, @
+
+ onPinchStart: (cb) -> @on(Events.PinchStart, cb)
+ onPinchEnd: (cb) -> @on(Events.PinchEnd, cb)
+ onPinch: (cb) -> @on(Events.Pinch, cb)
+ onRotateStart: (cb) -> @on(Events.RotateStart, cb)
+ onRotate: (cb) -> @on(Events.Rotate, cb)
+ onRotateEnd: (cb) -> @on(Events.RotateEnd, cb)
+ onScaleStart: (cb) -> @on(Events.ScaleStart, cb)
+ onScale: (cb) -> @on(Events.Scale, cb)
+ onScaleEnd: (cb) -> @on(Events.ScaleEnd, cb)
\ No newline at end of file
diff --git a/framer/LayerRotatable.coffee b/framer/LayerRotatable.coffee
deleted file mode 100644
index 39918da78..000000000
--- a/framer/LayerRotatable.coffee
+++ /dev/null
@@ -1,31 +0,0 @@
-{BaseClass} = require "./BaseClass"
-{Events} = require "./Events"
-{Gestures} = require "./Gestures"
-
-Events.RotateStart = Gestures.RotateStart
-Events.RotateEnd = Gestures.RotateEnd
-Events.Rotate = Gestures.Rotate
-
-class exports.LayerRotatable extends BaseClass
-
- @define "enabled", @simpleProperty("enabled", true)
-
- constructor: (@layer) ->
- super
- @layer.gestures.on(Gestures.RotateStart, @_rotateStart)
- @layer.gestures.on(Gestures.Rotate, @_rotate)
- @layer.gestures.on(Gestures.RotateEnd, @_rotateEnd)
-
- _rotateStart: (event) =>
- @_rotationStart = null
- @_rotationOffset = null
- @emit(Events.RotateStart, event)
-
- _rotate: (event) =>
- @_rotationStart ?= @layer.rotation
- @_rotationOffset ?= event.rotation
- @layer.rotation = event.rotation - @_rotationOffset + @_rotationStart
- @emit(Events.Rotate, event)
-
- _rotateEnd: (event) =>
- @emit(Events.RotateEnd, event)
\ No newline at end of file
diff --git a/framer/Utils.coffee b/framer/Utils.coffee
index 988fcbb04..dd99a1e0e 100644
--- a/framer/Utils.coffee
+++ b/framer/Utils.coffee
@@ -64,6 +64,10 @@ Utils.median = (x) ->
else
(sorted[(sorted.length / 2) - 1] + sorted[sorted.length / 2]) / 2
+Utils.nearestIncrement = (x, increment) ->
+ return x unless increment
+ return Math.round(x * (1 / increment)) / (1 / increment)
+
######################################################
# ANIMATION
@@ -286,7 +290,11 @@ Utils.isSafari = ->
(/safari/).test(navigator.userAgent.toLowerCase())
Utils.isTouch = ->
- window.ontouchstart is null
+ # This needs to be a little more extensive because we
+ # patch ontouchstart to fake Hammer
+ window.ontouchstart is null and
+ window.ontouchmove is null and
+ window.ontouchend is null
Utils.isDesktop = ->
Utils.deviceType() is "desktop"
@@ -540,6 +548,21 @@ Utils.loadImage = (url, callback, context) ->
# Point
+Utils.pointDivide = (pointA, pointB, fraction) ->
+ return point =
+ x: (pointA.x + pointB.x) / fraction
+ y: (pointA.y + pointB.y) / fraction
+
+Utils.pointAdd = (pointA, pointB) ->
+ return point =
+ x: pointA.x + pointB.x
+ y: pointA.y + pointB.y
+
+Utils.pointSubtract = (pointA, pointB) ->
+ return point =
+ x: pointA.x - pointB.x
+ y: pointA.y - pointB.y
+
Utils.pointZero = (args={}) ->
return _.defaults(args, {x:0, y:0})
@@ -578,6 +601,11 @@ Utils.pointInFrame = (point, frame) ->
return false if point.y < Utils.frameGetMinY(frame) or point.y > Utils.frameGetMaxY(frame)
return true
+Utils.pointCenter = (pointA, pointB) ->
+ return Utils.pointDivide(pointA, pointB, 2)
+
+
+
# Size
Utils.sizeZero = (args={}) ->
diff --git a/test/tests/LayerGesturesTest.coffee b/test/tests/LayerGesturesTest.coffee
index 9623cd0f9..f96bde5d3 100644
--- a/test/tests/LayerGesturesTest.coffee
+++ b/test/tests/LayerGesturesTest.coffee
@@ -45,7 +45,6 @@ describe "LayerGestures", ->
layer.emit Events.Pinch
pinchCount.should.equal 1
-
it "should only run once when using 'once'", ->
layerA = new Layer
@@ -71,4 +70,18 @@ describe "LayerGestures", ->
handler = -> console.log "hello"
layerA.on(Events.Pinch, handler)
layerA.removeAllListeners(Events.Pinch)
- layerA.listeners(Events.Pinch).length.should.equal 0
\ No newline at end of file
+ layerA.listeners(Events.Pinch).length.should.equal 0
+
+ it "should route a gesture event to the gesture event manager", ->
+
+ context = new Framer.Context name:"test123"
+
+ context.run ->
+ layerA = new Layer
+
+ layerA.on Gestures.Pinch, ->
+ layerA._domEventManager.listenerEvents().should.eql [
+ "touchstart", "touchmove", "touchend", "touchcancel", "mousedown"]
+
+ layerA.off Gestures.Pinch, ->
+ layerA._domEventManager.listenerEvents().should.eql []