Permalink
Browse files

Merge pull request #296 from koenbok/touchemulator

Touch Emulator
  • Loading branch information...
koenbok committed Jan 20, 2016
2 parents b5a7beb + 8b5a6ae commit fde7c54cb4564eb4a1516a75c91d5c4b5eae33dd
@@ -15,7 +15,6 @@
var iOS = /iPad|iPhone|iPod/.test(navigator.platform)
if (iOS) { viewport += ", shrink-to-fit=no" }
document.write("<meta name=\"viewport\" content=\"" + viewport + "\">")
if (window.ontouchstart == undefined) { window.ontouchstart = null }
})()
</script>
@@ -1,3 +1,4 @@
# exports.Hints = require "./Hints"
exports.TouchEmulator = require "./TouchEmulator"
exports.MobileScrollFix = require "./MobileScrollFix"
exports.OmitNew = require "./OmitNew"
exports.OmitNew = require "./OmitNew"
@@ -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()
@@ -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()
Framer.Extras.MobileScrollFix.enable() if Utils.isMobile()
Framer.Extras.TouchEmulator.enable()
@@ -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)
@@ -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
Oops, something went wrong.

0 comments on commit fde7c54

Please sign in to comment.