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
465
gaia/apps/system/js/edge_swipe_detector.js
Executable file
465
gaia/apps/system/js/edge_swipe_detector.js
Executable file
|
|
@ -0,0 +1,465 @@
|
|||
'use strict';
|
||||
/* global SettingsListener */
|
||||
/* global Service */
|
||||
/* global SheetsTransition */
|
||||
/* global StackManager */
|
||||
/* global TouchForwarder */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const kEdgeIntertia = 250;
|
||||
const kEdgeThreshold = 0.3;
|
||||
const kEdgeAngleThreshold = Math.PI / 6;
|
||||
const kSignificant = 10;
|
||||
|
||||
/**
|
||||
* Detects user gestures for moving between apps using edge gestures.
|
||||
* Gestures are listened for on the previous/next elements, which is
|
||||
* above the app container. Forwards events when they are not relevant to
|
||||
* the app iframe.
|
||||
* @class EdgeSwipeDetector
|
||||
*/
|
||||
function EdgeSwipeDetector() {}
|
||||
|
||||
EdgeSwipeDetector.prototype = {
|
||||
leftPanel: document.getElementById('left-panel'),
|
||||
rightPanel: document.getElementById('right-panel'),
|
||||
screen: document.getElementById('screen'),
|
||||
|
||||
_touchForwarder: null,
|
||||
|
||||
/**
|
||||
* Starts EdgeSwipeDetector and begins listening for events.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
start: function esd_init() {
|
||||
window.addEventListener('homescreenopened', this);
|
||||
window.addEventListener('appopened', this);
|
||||
window.addEventListener('cardviewclosed', this);
|
||||
window.addEventListener('mozChromeEvent', this);
|
||||
window.addEventListener('updatepromptshown', this);
|
||||
window.addEventListener('updateprompthidden', this);
|
||||
window.addEventListener('installpromptshown', this);
|
||||
window.addEventListener('installprompthidden', this);
|
||||
window.addEventListener('shrinking-start', this);
|
||||
window.addEventListener('shrinking-stop', this);
|
||||
window.addEventListener('hierarchychanged', this);
|
||||
window.addEventListener('hierarchytopmostwindowchanged', this);
|
||||
|
||||
['touchstart', 'touchmove', 'touchend',
|
||||
'mousedown', 'mousemove', 'mouseup'].forEach(function(e) {
|
||||
this.leftPanel.addEventListener(e, this);
|
||||
this.rightPanel.addEventListener(e, this);
|
||||
}, this);
|
||||
this._touchForwarder = new TouchForwarder();
|
||||
|
||||
SettingsListener.observe('edgesgesture.enabled', false,
|
||||
this.settingUpdate.bind(this));
|
||||
SettingsListener.observe('edgesgesture.debug', false,
|
||||
this.debugUpdate.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not the setting is enabled.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
_settingEnabled: false,
|
||||
|
||||
/**
|
||||
* Called when the edgesgesture.enabled setting is changed.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
settingUpdate: function esd_settingUpdate(enabled) {
|
||||
this._settingEnabled = enabled;
|
||||
this._updateEnabled();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the edgesgesture.debug setting is changed.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
debugUpdate: function esd_debugUpdate(enabled) {
|
||||
this.screen.classList.toggle('edges-debug', enabled);
|
||||
},
|
||||
|
||||
_lifecycleEnabled: false,
|
||||
|
||||
get lifecycleEnabled() {
|
||||
return this._lifecycleEnabled;
|
||||
},
|
||||
set lifecycleEnabled(enable) {
|
||||
this._lifecycleEnabled = enable;
|
||||
this._updateEnabled();
|
||||
},
|
||||
|
||||
/**
|
||||
* General event handler.
|
||||
* @param {Object} e
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
handleEvent: function esd_handleEvent(e) {
|
||||
switch (e.type) {
|
||||
case 'hierarchychanged':
|
||||
case 'hierarchytopmostwindowchanged':
|
||||
// XXX: Use this.appWindowManager instead if
|
||||
// we become part of appWindowManager submodules.
|
||||
// i.e., Service.query('getTopMostUI') === this.parent
|
||||
var topMostUI = Service.query('getTopMostUI');
|
||||
var topMostWindow = Service.query('getTopMostWindow');
|
||||
if (topMostUI && topMostUI.name === 'AppWindowManager') {
|
||||
if (topMostWindow &&
|
||||
!topMostWindow.getBottomMostWindow().isHomescreen &&
|
||||
!Service.query('isFtuRunning')) {
|
||||
this.lifecycleEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lifecycleEnabled = false;
|
||||
break;
|
||||
case 'mousedown':
|
||||
case 'mousemove':
|
||||
case 'mouseup':
|
||||
// Preventing gecko reflows until
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1005815 lands
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'touchstart':
|
||||
e.preventDefault();
|
||||
this._touchStart(e);
|
||||
break;
|
||||
case 'touchmove':
|
||||
e.preventDefault();
|
||||
this._touchMove(e);
|
||||
break;
|
||||
case 'touchend':
|
||||
e.preventDefault();
|
||||
this._touchEnd(e);
|
||||
break;
|
||||
case 'appopened':
|
||||
var app = e.detail;
|
||||
if (!app.stayBackground) {
|
||||
this.lifecycleEnabled =
|
||||
(app.origin !== Service.query('getFtuOrigin'));
|
||||
}
|
||||
break;
|
||||
case 'shrinking-start':
|
||||
this.lifecycleEnabled = false;
|
||||
break;
|
||||
case 'cardviewclosed':
|
||||
var targetApp = e.detail;
|
||||
// Enable the edge gestures if we switched back into any app except
|
||||
// the homescreen.
|
||||
if (!targetApp.isHomescreen) {
|
||||
this.lifecycleEnabled = true;
|
||||
}
|
||||
break;
|
||||
case 'mozChromeEvent':
|
||||
if (e.detail.type !== 'accessibility-control') {
|
||||
break;
|
||||
}
|
||||
var details = JSON.parse(e.detail.details);
|
||||
switch (details.eventType) {
|
||||
case 'edge-swipe-right':
|
||||
this.autoSwipe('ltr');
|
||||
break;
|
||||
case 'edge-swipe-left':
|
||||
this.autoSwipe('rtl');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'updatepromptshown':
|
||||
case 'installpromptshown':
|
||||
this.lifecycleEnabled = false;
|
||||
break;
|
||||
// XXX: Move install/update dialog into system dialog
|
||||
// and then we could remove this.
|
||||
case 'updateprompthidden':
|
||||
case 'installprompthidden':
|
||||
case 'shrinking-stop':
|
||||
if (Service.query('getTopMostWindow') &&
|
||||
!Service.query('getTopMostWindow').isHomescreen) {
|
||||
this.lifecycleEnabled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables or disables the previous/next triggers.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
_updateEnabled: function esd_updateEnabled() {
|
||||
var enabled = this._lifecycleEnabled && this._settingEnabled;
|
||||
this.leftPanel.classList.toggle('disabled', !enabled);
|
||||
this.rightPanel.classList.toggle('disabled', !enabled);
|
||||
|
||||
if (!enabled && this._touchStartEvt) {
|
||||
this._touchStartEvt = null; // we ignore the rest of the gesture
|
||||
SheetsTransition.snapInPlace();
|
||||
}
|
||||
},
|
||||
|
||||
_touchStartEvt: null,
|
||||
_startDate: null,
|
||||
_startX: null,
|
||||
_deltaX: null,
|
||||
_startY: null,
|
||||
_deltaY: null,
|
||||
_forwardTimeout: null,
|
||||
|
||||
_progress: null,
|
||||
_winWidth: null,
|
||||
_beganTransition: null,
|
||||
_moved: null,
|
||||
_direction: null,
|
||||
_forwarding: null,
|
||||
_redispatching: null,
|
||||
|
||||
_touchStart: function esd_touchStart(e) {
|
||||
this._winWidth = window.innerWidth;
|
||||
this._direction = (e.target == this.rightPanel) ? 'rtl' : 'ltr';
|
||||
this._touchStartEvt = e;
|
||||
this._startDate = Date.now();
|
||||
|
||||
var iframe = StackManager.getCurrent().getTopMostWindow().iframe;
|
||||
this._touchForwarder.destination = iframe;
|
||||
|
||||
var touch = e.changedTouches[0];
|
||||
this._startX = touch.clientX;
|
||||
this._startY = touch.clientY;
|
||||
this._deltaX = 0;
|
||||
this._deltaY = 0;
|
||||
this._beganTransition = false;
|
||||
this._moved = false;
|
||||
this._forwarding = false;
|
||||
this._redispatching = false;
|
||||
|
||||
this._clearForwardTimeout();
|
||||
this._forwardTimeout = setTimeout((function longTouch() {
|
||||
// Didn't move for a while after the touchstart,
|
||||
// this isn't a swipe
|
||||
this._forwardTimeout = null;
|
||||
this._forwarding = true;
|
||||
this._forward(this._touchStartEvt);
|
||||
}).bind(this), 300);
|
||||
},
|
||||
|
||||
_touchMove: function esd_touchMove(e) {
|
||||
if (!this._touchStartEvt) {
|
||||
return;
|
||||
}
|
||||
var touch = e.touches[0];
|
||||
this._updateProgress(touch);
|
||||
var delta = Math.max(Math.abs(this._deltaX), Math.abs(this._deltaY));
|
||||
|
||||
// If we already started forwarding we just continue
|
||||
if (this._forwarding) {
|
||||
this._forward(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a pinch gesture we start forwarding
|
||||
if (e.touches.length > 1) {
|
||||
this._startForwarding(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the gesture isn't horizontal we start forwarding
|
||||
if (delta > kSignificant && !this._horizontalGesture()) {
|
||||
this._startForwarding(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// After a small threshold...
|
||||
if ((this._deltaX < kSignificant || this._outsideApp(e)) &&
|
||||
!this._moved) {
|
||||
return;
|
||||
}
|
||||
|
||||
// preparing to move the sheets...
|
||||
if (!this._beganTransition) {
|
||||
SheetsTransition.begin(this._direction);
|
||||
this._clearForwardTimeout();
|
||||
this._beganTransition = true;
|
||||
}
|
||||
|
||||
SheetsTransition.moveInDirection(this._direction, this._progress);
|
||||
this._moved = true;
|
||||
},
|
||||
|
||||
_touchEnd: function esd_touchEnd(e) {
|
||||
if (!this._touchStartEvt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Edge gestures are never multi-touch
|
||||
var touches = e.touches.length + e.changedTouches.length;
|
||||
if (touches > 1 && !this._forwarding) {
|
||||
this._touchStartEvt = null;
|
||||
SheetsTransition.snapInPlace();
|
||||
return;
|
||||
}
|
||||
|
||||
var touch = e.changedTouches[0];
|
||||
this._updateProgress(touch);
|
||||
|
||||
if (this._forwarding) {
|
||||
this._forward(e);
|
||||
} else if ((this._deltaX < kSignificant) &&
|
||||
(this._deltaY < kSignificant)) {
|
||||
setTimeout(function(self, touchstart, touchend) {
|
||||
self._forward(touchstart);
|
||||
setTimeout(function() {
|
||||
self._forward(touchend);
|
||||
}, 100);
|
||||
}, 0, this, this._touchStartEvt, e);
|
||||
this._forwarding = true;
|
||||
}
|
||||
|
||||
this._clearForwardTimeout();
|
||||
|
||||
var deltaT = Date.now() - this._startDate;
|
||||
var speed = this._progress / deltaT; // progress / ms
|
||||
var inertia = speed * kEdgeIntertia; // ms of intertia
|
||||
var adjustedProgress = (this._progress + inertia);
|
||||
|
||||
if (adjustedProgress < kEdgeThreshold || this._forwarding) {
|
||||
SheetsTransition.snapInPlace();
|
||||
return;
|
||||
}
|
||||
|
||||
var direction = this._direction;
|
||||
if (direction === 'ltr') {
|
||||
SheetsTransition.snapLeft(speed);
|
||||
} else {
|
||||
SheetsTransition.snapRight(speed);
|
||||
}
|
||||
if (direction === document.documentElement.dir) {
|
||||
StackManager.goPrev();
|
||||
} else {
|
||||
StackManager.goNext();
|
||||
}
|
||||
},
|
||||
|
||||
_updateProgress: function esd_updateProgress(touch) {
|
||||
this._deltaX = touch.clientX - this._startX;
|
||||
this._deltaY = touch.clientY - this._startY;
|
||||
|
||||
if (this._direction == 'rtl') {
|
||||
this._deltaX *= -1;
|
||||
this._deltaY *= -1;
|
||||
}
|
||||
this._progress = this._deltaX / this._winWidth;
|
||||
},
|
||||
|
||||
_horizontalGesture: function esd_horizontalGesture() {
|
||||
var angle = Math.atan2(this._deltaX, this._deltaY);
|
||||
var horizontalAngle = Math.PI / 2;
|
||||
|
||||
return Math.abs(angle - horizontalAngle) < kEdgeAngleThreshold;
|
||||
},
|
||||
|
||||
_clearForwardTimeout: function esd_clearForwardTimeout() {
|
||||
if (this._forwardTimeout) {
|
||||
clearTimeout(this._forwardTimeout);
|
||||
this._forwardTimeout = null;
|
||||
}
|
||||
},
|
||||
|
||||
_startForwarding: function esd_startForwarding(e) {
|
||||
this._clearForwardTimeout();
|
||||
this._forwarding = true;
|
||||
this._forward(this._touchStartEvt);
|
||||
|
||||
this._forward(e);
|
||||
|
||||
if (this._beganTransition) {
|
||||
SheetsTransition.snapInPlace();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Once we identify the gesture as something other than an horizontal
|
||||
* swipe we replay and start forwarding the touch events:
|
||||
*
|
||||
* * either sending them to the current app through the sendTouchEvent
|
||||
* mozbrowser API
|
||||
* * or redispatching the events to the system app if they were outside
|
||||
* the app frame
|
||||
*
|
||||
* When redispatching, since we don't know on which element they should be
|
||||
* dispatched (the target is always the gesture-panel) we send a custom
|
||||
* event instead.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
_forward: function esd_forward(e) {
|
||||
if (this._moved) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We continue dispatching/forwarding where we started.
|
||||
if (this._outsideApp(e) || this._redispatching) {
|
||||
window.dispatchEvent(new CustomEvent('edge-touch-redispatch', {
|
||||
bubbles: true,
|
||||
detail: e
|
||||
}));
|
||||
this._redispatching = true;
|
||||
} else {
|
||||
this._touchForwarder.forward(e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Detects whether or not a touch event is outside of an app container.
|
||||
* @param {Object} e The relevant touch event.
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
_outsideApp: function esd_outsideApp(e) {
|
||||
var touch = (e.type === 'touchend') ? e.changedTouches[0] : e.touches[0];
|
||||
|
||||
var x = touch.pageX;
|
||||
var y = touch.pageY;
|
||||
|
||||
// In fullscreen_layout mode the app frame will always be fullscreen
|
||||
// but we still want to redispatch touch events to the "overlayed"
|
||||
// software home button
|
||||
var softwareButtonOverlayed =
|
||||
Service.query('getTopMostWindow') &&
|
||||
Service.query('getTopMostWindow').isFullScreenLayout();
|
||||
var width = Service.query('LayoutManager.width');
|
||||
var height = Service.query('LayoutManager.height');
|
||||
if (softwareButtonOverlayed) {
|
||||
var sbWidth = Service.query('SoftwareButtonManager.width');
|
||||
var sbHeight = Service.query('SoftwareButtonManager.height');
|
||||
return x > (width - sbWidth) || y > (height - sbHeight);
|
||||
}
|
||||
return (x > width || y > height);
|
||||
},
|
||||
|
||||
/**
|
||||
* Swipes a page when we receive an event from gecko.
|
||||
* @param {String} direction
|
||||
* @memberof EdgeSwipeDetector.prototype
|
||||
*/
|
||||
autoSwipe: function esd_autoSwipe(direction) {
|
||||
if (!this._lifecycleEnabled) {
|
||||
return;
|
||||
}
|
||||
SheetsTransition.begin(direction);
|
||||
if (direction === 'ltr') {
|
||||
SheetsTransition.snapLeft(1);
|
||||
} else {
|
||||
SheetsTransition.snapRight(1);
|
||||
}
|
||||
if (direction === document.documentElement.dir) {
|
||||
StackManager.goPrev();
|
||||
} else {
|
||||
StackManager.goNext();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.EdgeSwipeDetector = EdgeSwipeDetector;
|
||||
|
||||
}(window));
|
||||
Loading…
Reference in a new issue