Skip to content

Commit

Permalink
Merge pull request #296 from koenbok/touchemulator
Browse files Browse the repository at this point in the history
Touch Emulator
  • Loading branch information
koenbok committed Jan 20, 2016
2 parents b5a7beb + 8b5a6ae commit fde7c54
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 110 deletions.
1 change: 0 additions & 1 deletion extras/Studio.framer/index.html
Expand Up @@ -15,7 +15,6 @@
var iOS = /iPad|iPhone|iPod/.test(navigator.platform) var iOS = /iPad|iPhone|iPod/.test(navigator.platform)
if (iOS) { viewport += ", shrink-to-fit=no" } if (iOS) { viewport += ", shrink-to-fit=no" }
document.write("<meta name=\"viewport\" content=\"" + viewport + "\">") document.write("<meta name=\"viewport\" content=\"" + viewport + "\">")
if (window.ontouchstart == undefined) { window.ontouchstart = null }
})() })()
</script> </script>


Expand Down
3 changes: 2 additions & 1 deletion framer/Extras/Extras.coffee
@@ -1,3 +1,4 @@
# exports.Hints = require "./Hints" # exports.Hints = require "./Hints"
exports.TouchEmulator = require "./TouchEmulator"
exports.MobileScrollFix = require "./MobileScrollFix" exports.MobileScrollFix = require "./MobileScrollFix"
exports.OmitNew = require "./OmitNew" exports.OmitNew = require "./OmitNew"
206 changes: 206 additions & 0 deletions 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()
7 changes: 6 additions & 1 deletion framer/Framer.coffee
@@ -1,5 +1,9 @@
{_} = require "./Underscore" {_} = require "./Underscore"


# Before we do anything else, we need to patch touch events
if window.ontouchstart is undefined
window.ontouchstart = null

Framer = {} Framer = {}


# Root level modules # Root level modules
Expand Down Expand Up @@ -59,4 +63,5 @@ Framer.DefaultContext = new Framer.Context(name:"Default")
Framer.CurrentContext = Framer.DefaultContext Framer.CurrentContext = Framer.DefaultContext


# Fix for mobile scrolling # Fix for mobile scrolling
Framer.Extras.MobileScrollFix.enable() if Utils.isMobile() Framer.Extras.MobileScrollFix.enable() if Utils.isMobile()
Framer.Extras.TouchEmulator.enable()
54 changes: 13 additions & 41 deletions framer/GestureManager.coffee
Expand Up @@ -8,7 +8,6 @@ Utils = require "./Utils"
class exports.GestureManager extends EventEmitter class exports.GestureManager extends EventEmitter


constructor: (@layer) -> constructor: (@layer) ->
@_manager = Hammer(@layer._element)


once: (eventName, listener) => once: (eventName, listener) =>
super(eventName, listener) super(eventName, listener)
Expand All @@ -29,6 +28,8 @@ class exports.GestureManager extends EventEmitter
throw new Error("No event name defined") unless eventName throw new Error("No event name defined") unless eventName
throw new Error("No listener defined") unless listener throw new Error("No listener defined") unless listener


@_manager = Hammer(@layer._element)

# Make sure we have a hammer instance and layer listeners enabled # Make sure we have a hammer instance and layer listeners enabled
@layer.ignoreEvents = false @layer.ignoreEvents = false


Expand All @@ -53,7 +54,13 @@ class exports.GestureManager extends EventEmitter
@_manager.on(eventName, listener._actual) @_manager.on(eventName, listener._actual)


_removeListener: (eventName, listener) -> _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: -> destroy: ->
@_manager.destroy() @_manager.destroy()
Expand Down Expand Up @@ -152,51 +159,14 @@ class exports.GestureManager extends EventEmitter
if tap = @_manager.get(Gestures.Tap) if tap = @_manager.get(Gestures.Tap)
existingRecognizers.push(tap) existingRecognizers.push(tap)


return existingRecognizers 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)




############################################################## ##############################################################
# PATCH HAMMER # PATCH HAMMER


# This is a nasty monkey patch to get Hammer to use the DOMEventManager # 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) -> getWindowForElement = (element) ->
doc = element.ownerDocument or element doc = element.ownerDocument or element
Expand All @@ -207,11 +177,13 @@ splitStr = (str) ->


addEventListeners = (target, types, handler) -> addEventListeners = (target, types, handler) ->
splitStr(types).map (type) -> splitStr(types).map (type) ->
#console.log("hammer.addEventListener", type)
Framer.CurrentContext.domEventManager.wrap(target) Framer.CurrentContext.domEventManager.wrap(target)
.addEventListener(type, handler, false) .addEventListener(type, handler, false)


removeEventListeners = (target, types, handler) -> removeEventListeners = (target, types, handler) ->
splitStr(types).map (type) -> splitStr(types).map (type) ->
#console.log("hammer.removeEventListener", type)
Framer.CurrentContext.domEventManager.wrap(target) Framer.CurrentContext.domEventManager.wrap(target)
.removeEventListener(type, handler, false) .removeEventListener(type, handler, false)


Expand Down
6 changes: 6 additions & 0 deletions framer/Gestures.coffee
@@ -1,5 +1,6 @@
# Gesture events # Gesture events
Gestures = {} Gestures = {}
Gestures._prefix = "gesture:"


# Pan # Pan
Gestures.Pan = "pan" # This event includes all the other Pan events Gestures.Pan = "pan" # This event includes all the other Pan events
Expand Down Expand Up @@ -44,4 +45,9 @@ Gestures.Tap = "tap"
Gestures.SingleTap = "singletap" Gestures.SingleTap = "singletap"
Gestures.DoubleTap = "doubletap" 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 exports.Gestures = Gestures

0 comments on commit fde7c54

Please sign in to comment.