VulpesOS/gaia/tv_apps/remote-control-client/js/panel_element.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

323 lines
9.2 KiB
JavaScript
Executable file

'use strict';
(function(exports) {
var DEBUG = false;
var defaultOptions = {
touchReportPeriod: 60, // milliseconds
dblClickTimeThreshold: 250, // milliseconds
clickTimeThreshold: 200, // milliseconds
clickMoveThreshold: 10, // pixels
swipeMoveThreshold: 25, // pixels
touchingClass: null // CSS class
};
function PanelElement(elem, options) {
if (typeof options === 'function') {
options = {
handler: options
};
} else if (!options || typeof options !== 'object') {
options = {};
}
this.options = {};
for (var key in defaultOptions) {
this.options[key] = (typeof options[key] === 'undefined') ?
defaultOptions[key] : options[key];
}
if (typeof options.handler === 'function') {
this._handler = options.handler.bind(elem);
} else {
this._handler = function(type, detail) {};
}
this._element = elem;
this._hasMouseDown = false;
this._eventIdentifier = null;
this._startX = 0;
this._startY = 0;
this._prevDx = null;
this._prevDy = null;
this._startTime = 0;
this._waitForClickTimer = null;
this._pendingClickTimer = null;
this._reportTimer = null;
this._pendingMoveEvent = null;
this._touchIdentifier = null;
this._panelX = 0;
this._panelY = 0;
this._panelWidth = 0;
this._panelHeight = 0;
this._element.addEventListener('mousedown', this);
window.addEventListener('mousemove', this);
window.addEventListener('mouseup', this);
this._element.addEventListener('touchstart', this);
this._element.addEventListener('touchmove', this);
this._element.addEventListener('touchend', this);
window.addEventListener('resize', this);
this.updatePanelInfo();
}
PanelElement.prototype = {
uninit: function() {
window.removeEventListener('resize', this);
this._element.removeEventListener('touchend', this);
this._element.removeEventListener('touchmove', this);
this._element.removeEventListener('touchstart', this);
window.removeEventListener('mouseup', this);
window.removeEventListener('mousemove', this);
this._element.removeEventListener('mousedown', this);
this._element = null;
this._handler = null;
this.options = null;
},
updatePanelInfo: function() {
var rect = this._element.getBoundingClientRect();
this._panelX = Math.round(rect.left);
this._panelY = Math.round(rect.top);
this._panelWidth = Math.round(rect.width);
this._panelHeight = Math.round(rect.height);
},
handleEvent: function(evt) {
var touches, touch;
switch(evt.type) {
case 'resize':
this.updatePanelInfo();
break;
case 'mousedown':
if (evt.button === 0) {
this._hasMouseDown = true;
this.onStart(evt.clientX, evt.clientY);
evt.preventDefault();
}
break;
case 'mousemove':
if (this._hasMouseDown) {
this.onMove(evt.clientX, evt.clientY);
evt.preventDefault();
}
break;
case 'mouseup':
if (this._hasMouseDown && evt.button === 0) {
this._hasMouseDown = false;
this.onEnd(evt.clientX, evt.clientY);
evt.preventDefault();
}
break;
case 'touchstart':
if (this._touchIdentifier !== null) {
evt.preventDefault();
return;
}
touches = evt.changedTouches;
if (touches.length == 1) {
touch = touches[0];
this._touchIdentifier = touch.identifier;
this.onStart(touch.pageX - this._panelX,
touch.pageY - this._panelY);
evt.preventDefault();
}
break;
case 'touchmove':
case 'touchend':
touches = evt.changedTouches;
touch = null;
for(var i = 0; i < touches.length; i++) {
if (touches[i].identifier == this._touchIdentifier) {
touch = touches[i];
break;
}
}
if (touch) {
if (evt.type == 'touchend') {
this._touchIdentifier = null;
this.onEnd(touch.pageX - this._panelX,
touch.pageY - this._panelY);
} else {
this.onMove(touch.pageX - this._panelX,
touch.pageY - this._panelY);
}
}
evt.preventDefault();
break;
}
},
onStart: function(x, y) {
document.activeElement.blur();
if (this.options.touchingClass) {
this._element.classList.add(this.options.touchingClass);
}
this._startX = x;
this._startY = y;
this._startTime = Date.now();
var handleTouchStart = function() {
this._waitForClickTimer = null;
this.handleTouch('touchstart', 0, 0);
}.bind(this);
if (this.options.clickTimeThreshold) {
this._waitForClickTimer = setTimeout(handleTouchStart,
this.options.clickTimeThreshold);
} else {
handleTouchStart();
}
},
onMove: function(x, y) {
var dx = x - this._startX;
var dy = y - this._startY;
if (this._waitForClickTimer) {
var clickMoveThreshold = this.options.clickMoveThreshold;
if (Math.abs(dx) <= clickMoveThreshold &&
Math.abs(dy) <= clickMoveThreshold) {
return;
}
clearTimeout(this._waitForClickTimer);
this._waitForClickTimer = null;
this.handleTouch('touchstart', 0, 0);
}
this.handleTouch('touchmove', dx, dy);
},
onEnd: function(x, y) {
var dx = x - this._startX;
var dy = y - this._startY;
if (this._waitForClickTimer) {
clearTimeout(this._waitForClickTimer);
this._waitForClickTimer = null;
if (this.options.dblClickTimeThreshold) {
if (this._pendingClickTimer) {
clearTimeout(this._pendingClickTimer);
this._pendingClickTimer = null;
this.handleTouch('dblclick');
} else {
this._pendingClickTimer = setTimeout(function() {
this._pendingClickTimer = null;
this.handleTouch('click');
}.bind(this), this.options.dblClickTimeThreshold);
}
} else {
this.handleTouch('click');
}
} else {
var direction;
var distance = Math.round(Math.sqrt(dx * dx + dy * dy));
if (distance >= this.options.swipeMoveThreshold) {
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
if (angle < 0) {
angle += 360;
}
if (angle >= 315 || angle < 45) {
direction = 'right';
} else if (angle >= 45 && angle < 135) {
direction = 'down';
} else if (angle >= 135 && angle < 225) {
direction = 'left';
} else if (angle >= 225 && angle < 315) {
direction = 'up';
}
}
this.handleTouch('touchend', dx, dy, direction);
}
if (this.options.touchingClass) {
this._element.classList.remove(this.options.touchingClass);
}
},
handleTouch: function(type, dx, dy, swipe) {
if (DEBUG) {
console.log('[PanelElement] handling ' + type);
}
switch (type) {
case 'touchstart':
this._prevDx = undefined;
this._prevDy = undefined;
// A simple unique identifier for server to distinguish touch events
// between different clients.
this._eventIdentifier =
String(Date.now()) + String(Math.floor(Math.random() * 100));
this._handler(type, {
width: this._panelWidth,
height: this._panelHeight,
identifier: this._eventIdentifier
});
this._reportTimer = setInterval(function() {
if (this._pendingMoveEvent) {
this._handler(
this._pendingMoveEvent.type,
this._pendingMoveEvent.detail
);
this._pendingMoveEvent = null;
}
}.bind(this), this.options.touchReportPeriod);
break;
case 'touchmove':
if (dx === this._prevDx && dy === this._prevDy) {
return;
}
this._prevDx = dx;
this._prevDy = dy;
this._pendingMoveEvent = {
type: type,
detail: {
dx: dx,
dy: dy,
identifier: this._eventIdentifier,
duration: Date.now() - this._startTime
}
};
break;
case 'touchend':
this._pendingMoveEvent = null;
clearInterval(this._reportTimer);
this._reportTimer = null;
this._handler(type, {
dx: dx,
dy: dy,
identifier: this._eventIdentifier,
duration: Date.now() - this._startTime,
swipe: swipe
});
this._eventIdentifier = null;
break;
case 'click':
case 'dblclick':
this._handler(type, {});
break;
}
}
};
exports.PanelElement = PanelElement;
}(window));