VulpesOS/gaia/tv_apps/smart-system/js/input_transition.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

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);