Skip to content
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

0 comments on commit fde7c54

Please sign in to comment.