Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
305
gaia/distros/spark/apps/customizer/components/gesture/gesture.js
Executable file
305
gaia/distros/spark/apps/customizer/components/gesture/gesture.js
Executable file
|
|
@ -0,0 +1,305 @@
|
|||
/*jshint esnext:true*/
|
||||
;(function (g,n,f) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd){ define(f); }
|
||||
else { g[n]=f(); }
|
||||
}(this, 'Gesture', function() {
|
||||
|
||||
var Gesture = {
|
||||
DEBUG: false
|
||||
};
|
||||
|
||||
function debug(...args) {
|
||||
if (Gesture.DEBUG) {
|
||||
console.log('[Gesture]', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a promise that will resolve when the specified gesture is
|
||||
// detected. Note that the gesture is detected only once. You must call
|
||||
// this function again if you want to detect it again. The returned
|
||||
// promise will have a cancel() method on it. When called, gesture
|
||||
// detection stops and the promise is rejected.
|
||||
function detect(gestureSpec) {
|
||||
|
||||
// These are the states of the gesture detector
|
||||
const NOT_STARTED = 0; // We have not detected the gesture start yet
|
||||
const STARTED = 1; // Gesture has begun but has not finished
|
||||
const FINISHED = 2; // Gesture finished or was cancelled
|
||||
|
||||
var state; // Current gesture detection state
|
||||
var startEvent; // The touchstart event that started the gesture
|
||||
|
||||
var promise; // The promise we return;
|
||||
var promiseResolver; // The resolve function for the promise
|
||||
var promiseRejector; // The reject function for the promise
|
||||
|
||||
/*
|
||||
* Functions init(), start(), fail(), succeed(), cancel() and cleanup()
|
||||
* below handle state transitions between NOT_STARTED, STARTED and
|
||||
* FINISHED and also register and unregister event handlers, and
|
||||
* resolve or reject the promise.
|
||||
*/
|
||||
|
||||
// Call this function once to start listening.
|
||||
function init() {
|
||||
debug('Initializing');
|
||||
// Set our initial state
|
||||
state = NOT_STARTED;
|
||||
|
||||
// Register a capturing event listener for touchstart events.
|
||||
// This is the only event handler that is registered when
|
||||
// we are in the NOT_STARTED state.
|
||||
window.addEventListener('touchstart', touchstart, true);
|
||||
}
|
||||
|
||||
// Called when we detect that the gesture has started because
|
||||
// the right number of fingers are in the starting zone.
|
||||
// It registers handlers for touchmove and touchend and sets
|
||||
// the state to STARTED.
|
||||
function start() {
|
||||
debug('Start of gesture detected');
|
||||
state = STARTED;
|
||||
window.addEventListener('touchmove', touchmove, true);
|
||||
window.addEventListener('touchend', touchend, true);
|
||||
}
|
||||
|
||||
// This is called when the gesture fails (an additional finger
|
||||
// is added, for example, or the time limit is exceeded). It
|
||||
// unregisters the touchmove and touchend handlers and resets
|
||||
// the state to NOT_STARTED where it continues listening for new
|
||||
// touches.
|
||||
function fail(why) {
|
||||
debug('Gesture did not complete:', why);
|
||||
state = NOT_STARTED;
|
||||
window.removeEventListener('touchmove', touchmove, true);
|
||||
window.removeEventListener('touchend', touchend, true);
|
||||
}
|
||||
|
||||
// This is called when the gesture is successfully detected.
|
||||
// It resolves the promise and cleans up any listeners.
|
||||
// The data argument contains information about the gesture that
|
||||
// is used to resolve the promise.
|
||||
function succeed(data) {
|
||||
debug('Gesture completed:', data);
|
||||
cleanup();
|
||||
promiseResolver(data);
|
||||
}
|
||||
|
||||
// This is called if the caller cancels gesture detection.
|
||||
// It rejects the promise and cleans up any listeners
|
||||
function cancel() {
|
||||
cleanup();
|
||||
promiseRejector('cancelled');
|
||||
}
|
||||
|
||||
// Unregister all currently registered event listeners
|
||||
function cleanup() {
|
||||
state = FINISHED;
|
||||
window.removeEventListener('touchstart', touchstart, true);
|
||||
window.removeEventListener('touchmove', touchmove, true);
|
||||
window.removeEventListener('touchend', touchend, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* The touchstart(), touchmove(), touchend() functions below handle
|
||||
* touch events and call the state transition functions above as
|
||||
* appopriate.
|
||||
*/
|
||||
|
||||
// This is called whenever the user touches the screen.
|
||||
// But it doesn't do anything unless this event looks like the
|
||||
// start of a gesture.
|
||||
function touchstart(e) {
|
||||
debug('touch start', e.touches.length);
|
||||
// If a gesture has already started and another finger
|
||||
// goes down, then this is not the gesture we thought it was,
|
||||
// so we cancel it and go back to the initial state
|
||||
if (state === STARTED) {
|
||||
fail('already started');
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, check if the current touches meet the gesture
|
||||
// start condition, and switch to STARTED if so
|
||||
if (isGestureStart(e)) {
|
||||
startEvent = e;
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
function touchmove(e) {
|
||||
debug(e.type, e.touches.length,
|
||||
e.changedTouches[0].clientX, e.changedTouches[0].clientY);
|
||||
var gestureData;
|
||||
try {
|
||||
gestureData = isGestureEnd(startEvent, e);
|
||||
}
|
||||
catch(ex) {
|
||||
// If isGestureEnd throws an exception, the gesture fails.
|
||||
fail(ex.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// If isGestureEnd returns a truthy value, the gesture succeeds.
|
||||
if (gestureData) {
|
||||
succeed(gestureData);
|
||||
}
|
||||
|
||||
// Otherwise, we just keep listening for more move events
|
||||
}
|
||||
|
||||
// Handle touchend events in the same way that we handle touchmove events
|
||||
function touchend(e) {
|
||||
touchmove(e);
|
||||
}
|
||||
|
||||
// Return true if this event marks the start of the specified gesture
|
||||
function isGestureStart(event) {
|
||||
return Detectors[gestureSpec.type].isGestureStart(gestureSpec,
|
||||
event);
|
||||
}
|
||||
|
||||
function isGestureEnd(startEvent, endEvent) {
|
||||
return Detectors[gestureSpec.type].isGestureEnd(gestureSpec,
|
||||
startEvent,
|
||||
endEvent);
|
||||
}
|
||||
|
||||
// Make sure we know how to detect the specified gesture
|
||||
if (!Detectors[gestureSpec.type]) {
|
||||
return Promise.reject('Unsupported gesture: ' + gestureSpec.type);
|
||||
}
|
||||
|
||||
// If we do, then create the promise that we will return
|
||||
// and start detecting gestures when the promise is ready
|
||||
|
||||
promise = new Promise(function(resolve, reject) {
|
||||
// Store resolve and reject in the outer scope where we can use them
|
||||
promiseResolver = resolve;
|
||||
promiseRejector = reject;
|
||||
init();
|
||||
});
|
||||
|
||||
// Add a method to the returned promise that allows the client to
|
||||
// stop listening for the gesture
|
||||
promise.cancel = cancel;
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
// A utility function to determine whether the Touch t is in the region r.
|
||||
// Regions are specified in units relative to the window size.
|
||||
// (0,0) is the top-left corner of the window and (1,1) is the bottom right.
|
||||
function touchInRegion(t, r) {
|
||||
var x = t.clientX / window.innerWidth;
|
||||
var y = t.clientY / window.innerHeight;
|
||||
return x >= r.x0 && x <= r.x1 && y >= r.y0 && y <= r.y1;
|
||||
}
|
||||
|
||||
var Detectors = {
|
||||
swipe: {
|
||||
isGestureStart: function isSwipeStart(spec, event) {
|
||||
var touches = event.touches;
|
||||
|
||||
// If the number of touches is not correct, this is not a gesture start
|
||||
if (touches.length !== spec.numFingers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If any of the touches is in the wrong place,
|
||||
// this is not a gesture start
|
||||
if (spec.startRegion) {
|
||||
for(var i = 0; i < touches.length; i++) {
|
||||
var t = touches[i];
|
||||
if (!touchInRegion(t, spec.startRegion)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The gesture has started
|
||||
return true;
|
||||
},
|
||||
|
||||
//
|
||||
// This function tests whether the endEvent marks the end of the specified
|
||||
// gesture that started with the startEvent. There are three possible
|
||||
// outcomes:
|
||||
//
|
||||
// - if this is the end of the gesture, this function returns an object
|
||||
// that holds information about the gesture, and that object will be
|
||||
// used to resolve the promise.
|
||||
//
|
||||
// - if endEvent is not the end of the gesture, this function returns
|
||||
// false
|
||||
//
|
||||
// - if endEvent is indicates that the gesture has failed (for example
|
||||
// if the user's fingers are moving in the wrong direction) then this
|
||||
// function should throw an error which will cause the gesture detector
|
||||
// to reset to its NOT_STARTED state
|
||||
//
|
||||
isGestureEnd: function isSwipeEnd(spec, startEvent, endEvent) {
|
||||
var dt = endEvent.timeStamp - startEvent.timeStamp;
|
||||
var startTouches = startEvent.touches;
|
||||
var endTouches = endEvent.touches;
|
||||
|
||||
// The gesture fails if:
|
||||
// 1) it has taken too long
|
||||
// 2) a finger is lifted before success
|
||||
// 3) there are the wrong number of touches (that should not happen).
|
||||
if (spec.maxTime && dt > spec.maxTime) {
|
||||
throw new Error('timeout:' + dt);
|
||||
}
|
||||
if (endEvent.type === 'touchend') {
|
||||
throw new Error('touchend');
|
||||
}
|
||||
if (endTouches.length !== startTouches.length) {
|
||||
// This should not happen, but it does happen for unknown reasons
|
||||
// See bug 1139575. If we throw an error here it aborts the gesture
|
||||
// and makes it hard for the user to swipe on some devices.
|
||||
// So instead we just return false and act like it never happened
|
||||
// throw new Error('wrong number of touches');
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = {
|
||||
dt: dt,
|
||||
fingers: []
|
||||
};
|
||||
|
||||
for(var i = 0; i < startTouches.length; i++) {
|
||||
var st = startTouches[i];
|
||||
var et = endTouches.identifiedTouch(st.identifier);
|
||||
if (!et) { // this should not happen
|
||||
throw new Error('mismatch');
|
||||
}
|
||||
|
||||
// If the gesture spec specifies and endRegion and this touch is
|
||||
// not in it, then this is not the end of the gesture
|
||||
// (For some types of gestures it might be better to specify
|
||||
// a minimum and maximum required distance in each direction
|
||||
// instead of a end region.)
|
||||
if (spec.endRegion && !touchInRegion(et, spec.endRegion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This touch passes the test, so record it in the data
|
||||
data.fingers.push({
|
||||
x0: st.clientX,
|
||||
y0: st.clientY,
|
||||
x1: et.clientX,
|
||||
y1: et.clientY
|
||||
});
|
||||
}
|
||||
|
||||
// If all the touches were okay, then we've found the end of the gesture
|
||||
return data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Gesture.detect = detect;
|
||||
return Gesture;
|
||||
}));
|
||||
Loading…
Reference in a new issue