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 []