Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
229 lines
6.2 KiB
JavaScript
Executable file
229 lines
6.2 KiB
JavaScript
Executable file
'use strict';
|
|
|
|
(function(exports) {
|
|
|
|
/**
|
|
* InputAppsTransitionManager takes care of the visual life cycle of
|
|
* the keyboard container. The container basically cycles through
|
|
* the four defined states.
|
|
*
|
|
* Note that there isn't a show() method because the input management
|
|
* currently rely on keyboard app to call resize() first.
|
|
*
|
|
*/
|
|
var InputAppsTransitionManager = function() {
|
|
this._started = false;
|
|
this.currentState = undefined;
|
|
this.occupyingHeight = undefined;
|
|
this._element = null;
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype.onstatechange = null;
|
|
|
|
InputAppsTransitionManager.prototype.ELEMENT_ID = 'keyboards';
|
|
|
|
InputAppsTransitionManager.prototype.start = function() {
|
|
if (this._started) {
|
|
throw new Error('InputAppsTransitionManager: ' +
|
|
'Instance should not be start()\'ed twice.');
|
|
}
|
|
this._started = true;
|
|
|
|
this._element = document.getElementById(this.ELEMENT_ID);
|
|
if (!this._element) {
|
|
throw new Error('InputAppsTransitionManager: element not found.');
|
|
}
|
|
|
|
this._element.addEventListener('transitionend', this, true);
|
|
|
|
// Start with hidden state.
|
|
this.currentState = this.STATE_HIDDEN;
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype.stop = function hb_stop() {
|
|
if (!this._started) {
|
|
throw new Error('InputAppsTransitionManager: ' +
|
|
'Instance was never start()\'ed but stop() is called.');
|
|
}
|
|
this._started = false;
|
|
|
|
this._element.removeEventListener('transitionend', this, true);
|
|
|
|
this.currentState = undefined;
|
|
this.occupyingHeight = undefined;
|
|
this._element = null;
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype.handleEvent = function(evt) {
|
|
// We only care about transition: transform
|
|
if (evt.propertyName !== 'transform') {
|
|
return;
|
|
}
|
|
|
|
switch (this.currentState) {
|
|
case this.STATE_HIDDEN:
|
|
case this.STATE_VISIBLE:
|
|
// These should never happpen.
|
|
console.error('InputAppsTransitionManager: ' +
|
|
'received transitionend event during incorrect states.');
|
|
|
|
break;
|
|
|
|
case this.STATE_TRANSITION_IN:
|
|
this._changeState(this.STATE_VISIBLE);
|
|
|
|
break;
|
|
|
|
case this.STATE_TRANSITION_OUT:
|
|
this.occupyingHeight = undefined;
|
|
this._changeState(this.STATE_HIDDEN);
|
|
|
|
break;
|
|
}
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype.STATE_HIDDEN = 0;
|
|
InputAppsTransitionManager.prototype.STATE_VISIBLE = 1;
|
|
InputAppsTransitionManager.prototype.STATE_TRANSITION_IN = 2;
|
|
InputAppsTransitionManager.prototype.STATE_TRANSITION_OUT = 3;
|
|
|
|
InputAppsTransitionManager.prototype.handleResize = function(height) {
|
|
switch (this.currentState) {
|
|
case this.STATE_HIDDEN:
|
|
case this.STATE_TRANSITION_OUT:
|
|
this._setOccupyingHeight(height);
|
|
this._changeState(this.STATE_TRANSITION_IN);
|
|
|
|
// Start or revert transition
|
|
this._element.classList.remove('no-transition');
|
|
this._element.classList.remove('hide');
|
|
|
|
break;
|
|
|
|
case this.STATE_VISIBLE:
|
|
if (height === this.occupyingHeight) {
|
|
return;
|
|
|
|
}
|
|
this._setOccupyingHeight(height);
|
|
// No state change, but we need to publish a event.
|
|
this._publish('keyboardchange');
|
|
|
|
break;
|
|
|
|
case this.STATE_TRANSITION_IN:
|
|
// No state change, simply update the cached height.
|
|
this._setOccupyingHeight(height);
|
|
|
|
break;
|
|
}
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype.hide = function() {
|
|
if (this.currentState === this.STATE_TRANSITION_OUT ||
|
|
this.currentState === this.STATE_HIDDEN) {
|
|
// Do nothing.
|
|
return;
|
|
}
|
|
// No matter what the current state is, we now enters transition out state.
|
|
this._changeState(this.STATE_TRANSITION_OUT);
|
|
|
|
// Start hide transition
|
|
this._element.classList.remove('no-transition');
|
|
this._element.classList.add('hide');
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype.hideImmediately = function() {
|
|
if (this.currentState === this.STATE_HIDDEN) {
|
|
// Do nothing.
|
|
return;
|
|
}
|
|
// No matter what the current state is, we now enters transition out state.
|
|
this._changeState(this.STATE_TRANSITION_OUT);
|
|
|
|
// hide without transition
|
|
this._element.classList.add('no-transition');
|
|
this._element.classList.add('hide');
|
|
|
|
this._changeState(this.STATE_HIDDEN);
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype._changeState = function(state) {
|
|
if (state === this.currentState) {
|
|
// Should not call onstatechange and dispatch events.
|
|
return;
|
|
}
|
|
|
|
this.currentState = state;
|
|
if (typeof this.onstatechange === 'function') {
|
|
this.onstatechange();
|
|
}
|
|
|
|
// Publish some events.
|
|
switch (state) {
|
|
case this.STATE_HIDDEN:
|
|
this._publish('keyboardhidden');
|
|
|
|
break;
|
|
|
|
case this.STATE_VISIBLE:
|
|
this._publish('keyboardchange');
|
|
|
|
break;
|
|
|
|
case this.STATE_TRANSITION_IN:
|
|
// No event in for this state change
|
|
|
|
break;
|
|
|
|
case this.STATE_TRANSITION_OUT:
|
|
this._publish('keyboardhide');
|
|
|
|
break;
|
|
}
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype._publish = function (type) {
|
|
var eventInitDict = {
|
|
bubbles: true,
|
|
cancellable: true,
|
|
detail: {
|
|
height: this.occupyingHeight
|
|
}
|
|
};
|
|
|
|
// We dispatch the events at the body level so we are able to intercept
|
|
// them and prevent page resizing where desired.
|
|
var evt = new CustomEvent(type, eventInitDict);
|
|
document.body.dispatchEvent(evt);
|
|
};
|
|
|
|
// wrap it so we can mock it for testing
|
|
InputAppsTransitionManager.prototype._getDpx = function () {
|
|
return window.devicePixelRatio;
|
|
};
|
|
|
|
InputAppsTransitionManager.prototype._setOccupyingHeight = function (height) {
|
|
// bug 1059683: when we're on a HiDPI device with non-integer devicePixelRatio
|
|
// the system may calculate (from available screen height and keyboard height)
|
|
// the available height for current window/layout that is a fraction smaller
|
|
// than the ideal value, which can result in a 1-device-px gap between the
|
|
// current window/layout and keyboard, on such devices.
|
|
// to mitigate this, the keyboard tries to report 1 less pixel of height if
|
|
// it sees that the height of the keyboard is a fraction when expressed in
|
|
// device pixel.
|
|
|
|
var dpx = this._getDpx();
|
|
if ((height * dpx) % 1 !== 0) {
|
|
height = Math.floor(height * dpx) / dpx;
|
|
}
|
|
|
|
// XXX: On TV we got 1 additional px. We need to find out the root cause.
|
|
--height;
|
|
|
|
this.occupyingHeight = height;
|
|
};
|
|
|
|
exports.InputAppsTransitionManager = InputAppsTransitionManager;
|
|
|
|
})(window);
|