diff --git a/raspberry/roberto/views/frontend/routes.py b/raspberry/roberto/views/frontend/routes.py
index 4d4c76c..6ce7441 100644
--- a/raspberry/roberto/views/frontend/routes.py
+++ b/raspberry/roberto/views/frontend/routes.py
@@ -32,4 +32,8 @@ def index():
@frontend_blueprint.route('/gamepad.js')
def gamepad_js():
- return render_template('gamepad.js')
+ return render_template('gamepad.js'), {'Content-Type': 'text/javascript'}
+
+@frontend_blueprint.route('/virtualjoystick.js')
+def virtualjoystick_js():
+ return render_template('virtualjoystick.js'), {'Content-Type': 'text/javascript'}
diff --git a/raspberry/roberto/views/frontend/templates/gamepad.js b/raspberry/roberto/views/frontend/templates/gamepad.js
index d261a32..b0a30bd 100644
--- a/raspberry/roberto/views/frontend/templates/gamepad.js
+++ b/raspberry/roberto/views/frontend/templates/gamepad.js
@@ -109,6 +109,7 @@ function updateStatus() {
axesChanged = true;
}
}
+
if (axesChanged) {
gp_socket.emit('axes', {0: controller.axes[0], 1: controller.axes[1], 3: controller.axes[3], 4: controller.axes[4]});
}
@@ -134,3 +135,64 @@ if (haveEvents) {
} else {
setInterval(scangamepads, 500);
}
+
+// Virtual Joystick
+ var joystick = new VirtualJoystick({
+ container: document.getElementById('controls'),
+ mouseSupport: true,
+ stationaryBase: true,
+ baseX: 250,
+ baseY: window.innerHeight-300,
+ limitStickTravel: true,
+ stickRadius: 120,
+ strokeStyle: 'cyan'
+ });
+
+ var joystick2 = new VirtualJoystick({
+ container: document.getElementById('controls'),
+ mouseSupport: true,
+ stationaryBase: true,
+ baseX: window.innerWidth-250,
+ baseY: window.innerHeight-300,
+ limitStickTravel: true,
+ stickRadius: 120,
+ strokeStyle: 'orange'
+ });
+
+ joystick.addEventListener('touchStartValidation', function(event){
+ var touch = event.changedTouches[0];
+ if( touch.pageX >= window.innerWidth/2 ) return false;
+ return true
+ });
+ joystick.addEventListener('mouseStartValidation', function(event){
+ if( event.clientX >= window.innerWidth/2 ) return false;
+ return true
+ });
+
+
+ joystick2.addEventListener('touchStartValidation', function(event){
+ var touch = event.changedTouches[0];
+ if( touch.pageX < window.innerWidth/2 ) return false;
+ return true
+ });
+ joystick2.addEventListener('mouseStartValidation', function(event){
+ if( event.clientX < window.innerWidth/2 ) return false;
+ return true
+ });
+/*
+ joystick2.addEventListener('touchStart', function(){
+ console.log('fire')
+ })
+*/
+
+setInterval(function(){
+ if (joystick && joystick2) {
+ if (joystick._pressed === true || joystick2._pressed === true) {
+ var dx = joystick.deltaX() / joystick._stickRadius;
+ var dy = joystick.deltaY() / joystick._stickRadius;
+ var dx2 = joystick2.deltaX() / joystick2._stickRadius;
+ var dy2 = joystick2.deltaY() / joystick2._stickRadius;
+ gp_socket.emit('axes', {0: dx, 1: dy, 3: dx2, 4: dy2});
+ }
+ }
+}, 50);
diff --git a/raspberry/roberto/views/frontend/templates/index.html b/raspberry/roberto/views/frontend/templates/index.html
index 8cef378..7f9fad1 100644
--- a/raspberry/roberto/views/frontend/templates/index.html
+++ b/raspberry/roberto/views/frontend/templates/index.html
@@ -3,9 +3,27 @@
- Video Streaming Demonstration
+ Roberto
+
Video Streaming Demonstration
@@ -13,21 +31,12 @@
+
+
- Send:
-
-
- Receive:
-
- Press a button on your controller to start
+
@@ -133,27 +142,17 @@ socket.on('data', (data) => {
var vidstream = document.getElementById("stream");
var config = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] };
playStream(vidstream, null, null, null, config, function (errmsg) { console.error(errmsg); });
+
+ var canvas = document.getElementById("textoverlay");
+ var ctx = canvas.getContext("2d");
+ ctx.font = "24px Arial";
+ ctx.fillStyle = "blue";
+ ctx.fillText("Hello World", canvas.width/3, 30);
};
-
+
diff --git a/raspberry/roberto/views/frontend/templates/virtualjoystick.js b/raspberry/roberto/views/frontend/templates/virtualjoystick.js
new file mode 100644
index 0000000..1aa7f15
--- /dev/null
+++ b/raspberry/roberto/views/frontend/templates/virtualjoystick.js
@@ -0,0 +1,476 @@
+var VirtualJoystick = function(opts)
+{
+ opts = opts || {};
+ this._container = opts.container || document.body;
+ this._strokeStyle = opts.strokeStyle || 'cyan';
+ this._stickEl = opts.stickElement || this._buildJoystickStick();
+ this._baseEl = opts.baseElement || this._buildJoystickBase();
+ this._mouseSupport = opts.mouseSupport !== undefined ? opts.mouseSupport : false;
+ this._stationaryBase = opts.stationaryBase || false;
+ this._baseX = this._stickX = opts.baseX || 0;
+ this._baseY = this._stickY = opts.baseY || 0;
+ this._limitStickTravel = opts.limitStickTravel || false;
+ this._stickRadius = opts.stickRadius !== undefined ? opts.stickRadius : 100;
+ this._useCssTransform = opts.useCssTransform !== undefined ? opts.useCssTransform : false;
+ this._show = opts.show || false;
+ this._releaseX = opts.releaseX !== undefined ? opts.releaseX : null;
+ this._releaseY = opts.releaseY !== undefined ? opts.releaseY : null;
+
+ this._container.style.position = "relative";
+ this._container.appendChild(this._baseEl);
+ this._baseEl.style.position = "absolute";
+ this._baseEl.style.display = "none";
+ this._container.appendChild(this._stickEl);
+ this._stickEl.style.position = "absolute";
+ this._stickEl.style.display = "none";
+
+ this._pressed = false;
+ this._touchIdx = null;
+
+ if(this._stationaryBase === true){
+ this._baseEl.style.display = "";
+ this._baseEl.style.left = (this._baseX - this._baseEl.width /2)+"px";
+ this._baseEl.style.top = (this._baseY - this._baseEl.height/2)+"px";
+ }
+
+ this._transform = this._useCssTransform ? this._getTransformProperty() : false;
+ this._has3d = this._check3D();
+
+ this._updatePositionOfContainer();
+ window.onresize = (function (joystick) {
+ return function () {
+ return joystick._updatePositionOfContainer();
+ }
+ })(this);
+
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ this._$onTouchStart = __bind(this._onTouchStart , this);
+ this._$onTouchEnd = __bind(this._onTouchEnd , this);
+ this._$onTouchMove = __bind(this._onTouchMove , this);
+ this._container.addEventListener( 'touchstart' , this._$onTouchStart , false );
+ this._container.addEventListener( 'touchend' , this._$onTouchEnd , false );
+ this._container.addEventListener( 'touchmove' , this._$onTouchMove , false );
+ if( this._mouseSupport ){
+ this._$onMouseDown = __bind(this._onMouseDown , this);
+ this._$onMouseUp = __bind(this._onMouseUp , this);
+ this._$onMouseMove = __bind(this._onMouseMove , this);
+ this._container.addEventListener( 'mousedown' , this._$onMouseDown , false );
+ this._container.addEventListener( 'mouseup' , this._$onMouseUp , false );
+ this._container.addEventListener( 'mousemove' , this._$onMouseMove , false );
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// //
+//////////////////////////////////////////////////////////////////////////////////
+
+VirtualJoystick.prototype.destroy = function()
+{
+ this._container.removeChild(this._baseEl);
+ this._container.removeChild(this._stickEl);
+
+ this._container.removeEventListener( 'touchstart' , this._$onTouchStart , false );
+ this._container.removeEventListener( 'touchend' , this._$onTouchEnd , false );
+ this._container.removeEventListener( 'touchmove' , this._$onTouchMove , false );
+ if( this._mouseSupport ){
+ this._container.removeEventListener( 'mouseup' , this._$onMouseUp , false );
+ this._container.removeEventListener( 'mousedown' , this._$onMouseDown , false );
+ this._container.removeEventListener( 'mousemove' , this._$onMouseMove , false );
+ }
+}
+
+/**
+ * @returns {Boolean} true if touchscreen is currently available, false otherwise
+*/
+VirtualJoystick.touchScreenAvailable = function()
+{
+ return 'createTouch' in document ? true : false;
+}
+
+/**
+ * microevents.js - https://github.com/jeromeetienne/microevent.js
+*/
+;(function(destObj){
+ destObj.addEventListener = function(event, fct){
+ if(this._events === undefined) this._events = {};
+ this._events[event] = this._events[event] || [];
+ this._events[event].push(fct);
+ return fct;
+ };
+ destObj.removeEventListener = function(event, fct){
+ if(this._events === undefined) this._events = {};
+ if( event in this._events === false ) return;
+ this._events[event].splice(this._events[event].indexOf(fct), 1);
+ };
+ destObj.dispatchEvent = function(event /* , args... */){
+ if(this._events === undefined) this._events = {};
+ if( this._events[event] === undefined ) return;
+ var tmpArray = this._events[event].slice();
+ for(var i = 0; i < tmpArray.length; i++){
+ var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1))
+ if( result !== undefined ) return result;
+ }
+ return undefined
+ };
+})(VirtualJoystick.prototype);
+
+//////////////////////////////////////////////////////////////////////////////////
+// //
+//////////////////////////////////////////////////////////////////////////////////
+
+VirtualJoystick.prototype.deltaX = function(){ return this._stickX - this._baseX; }
+VirtualJoystick.prototype.deltaY = function(){ return this._stickY - this._baseY; }
+
+VirtualJoystick.prototype.up = function(){
+ if( this._pressed === false ) return false;
+ var deltaX = this.deltaX();
+ var deltaY = this.deltaY();
+ if( deltaY >= 0 ) return false;
+ if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false;
+ return true;
+}
+VirtualJoystick.prototype.down = function(){
+ if( this._pressed === false ) return false;
+ var deltaX = this.deltaX();
+ var deltaY = this.deltaY();
+ if( deltaY <= 0 ) return false;
+ if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false;
+ return true;
+}
+VirtualJoystick.prototype.right = function(){
+ if( this._pressed === false ) return false;
+ var deltaX = this.deltaX();
+ var deltaY = this.deltaY();
+ if( deltaX <= 0 ) return false;
+ if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false;
+ return true;
+}
+VirtualJoystick.prototype.left = function(){
+ if( this._pressed === false ) return false;
+ var deltaX = this.deltaX();
+ var deltaY = this.deltaY();
+ if( deltaX >= 0 ) return false;
+ if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false;
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// Dealing with touches/mouse movents
+//////////////////////////////////////////////////////////////////////////////////
+
+VirtualJoystick.prototype._UpdateBase = function(){
+ this._move(this._baseEl.style, (this._baseX - this._baseEl.width /2), (this._baseY - this._baseEl.height/2));
+}
+VirtualJoystick.prototype._UpdateStick = function(){
+ this._move(this._stickEl.style, (this._stickX - this._stickEl.width /2), (this._stickY - this._stickEl.height/2));
+}
+
+
+VirtualJoystick.prototype._onUp = function()
+{
+ this._pressed = false;
+ this._show ? this._stickEl.style.display = "" : this._stickEl.style.display = "none";
+ if(!(this._stationaryBase || this._show)) { this._baseEl.style.display = "none"; }
+
+ if(this._releaseX !=null || this._releaseY !=null){
+ if (this._releaseX !=null){
+ this._stickX= this._baseX + this._releaseX;
+ }
+ if (this._releaseY !=null){
+ this._stickY= this._baseY + this._releaseY;
+ }
+ }
+ else {
+ this._stickX = this._baseX;
+ this._stickY = this._baseY;
+ }
+ this._UpdateStick()
+}
+
+
+VirtualJoystick.prototype._onDown = function(x, y)
+{
+ this._pressed = true;
+ if(this._stationaryBase == false){
+ this._baseX = x;
+ this._baseY = y;
+ this._baseEl.style.display = "";
+ this._UpdateBase()
+ }
+
+ this._stickX = x;
+ this._stickY = y;
+
+ if(this._limitStickTravel === true){
+ var deltaX = this.deltaX();
+ var deltaY = this.deltaY();
+ var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) );
+ if(stickDistance > this._stickRadius){
+ var stickNormalizedX = deltaX / stickDistance;
+ var stickNormalizedY = deltaY / stickDistance;
+
+ this._stickX = stickNormalizedX * this._stickRadius + this._baseX;
+ this._stickY = stickNormalizedY * this._stickRadius + this._baseY;
+ }
+ }
+
+ this._stickEl.style.display = "";
+ this._UpdateStick()
+}
+
+VirtualJoystick.prototype._onMove = function(x, y)
+{
+ if( this._pressed === true ){
+ this._stickX = x;
+ this._stickY = y;
+
+ if(this._limitStickTravel === true){
+ var deltaX = this.deltaX();
+ var deltaY = this.deltaY();
+ var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) );
+ if(stickDistance > this._stickRadius){
+ var stickNormalizedX = deltaX / stickDistance;
+ var stickNormalizedY = deltaY / stickDistance;
+
+ this._stickX = stickNormalizedX * this._stickRadius + this._baseX;
+ this._stickY = stickNormalizedY * this._stickRadius + this._baseY;
+ }
+ }
+ this._UpdateStick()
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// bind touch events (and mouse events for debug) //
+// mouse events
+//////////////////////////////////////////////////////////////////////////////////
+// container offset.
+VirtualJoystick.prototype._updatePositionOfContainer = function () {
+ var x = 0, y = 0;
+ var element = this._container;
+ if(this._stationaryBase === true){
+ this._UpdateBase()
+ while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
+ x += element.offsetLeft;
+ y += element.offsetTop;
+ element = element.offsetParent;
+ }
+ }
+ else {
+ while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
+ x += element.offsetLeft - element.scrollLeft;
+ y += element.offsetTop - element.scrollTop;
+ element = element.offsetParent;
+ }
+ }
+ this._containerX = x;
+ this._containerY = y;
+}
+
+VirtualJoystick.prototype._onMouseUp = function(event)
+{
+ return this._onUp();
+}
+
+VirtualJoystick.prototype._onMouseDown = function(event)
+{
+ // notify event for validation
+ var isValid = this.dispatchEvent('mouseStartValidation', event);
+ if( isValid === false ) return;
+
+ event.preventDefault();
+ var x = event.clientX - this._containerX;
+ var y = event.clientY - this._containerY;
+ return this._onDown(x, y);
+}
+
+VirtualJoystick.prototype._onMouseMove = function(event)
+{
+ var x = event.clientX - this._containerX;
+ var y = event.clientY - this._containerY;
+ return this._onMove(x, y);
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// touch events //
+//////////////////////////////////////////////////////////////////////////////////
+
+VirtualJoystick.prototype._onTouchStart = function(event)
+{
+ // if there is already a touch inprogress do nothing
+ if( this._touchIdx !== null ) return;
+
+ // notify event for validation
+ var isValid = this.dispatchEvent('touchStartValidation', event);
+ if( isValid === false ) return;
+
+ // dispatch touchStart
+ this.dispatchEvent('touchStart', event);
+
+ event.preventDefault();
+ // get the first who changed
+ var touch = event.changedTouches[0];
+ // set the touchIdx of this joystick
+ this._touchIdx = touch.identifier;
+
+ // forward the action
+ var x = touch.pageX - this._containerX;
+ var y = touch.pageY - this._containerY;
+ return this._onDown(x, y)
+}
+
+VirtualJoystick.prototype._onTouchEnd = function(event)
+{
+ // if there is no touch in progress, do nothing
+ if( this._touchIdx === null ) return;
+
+ // dispatch touchEnd
+ this.dispatchEvent('touchEnd', event);
+
+ // try to find our touch event
+ var touchList = event.changedTouches;
+ for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++);
+ // if touch event isnt found,
+ if( i === touchList.length) return;
+
+ // reset touchIdx - mark it as no-touch-in-progress
+ this._touchIdx = null;
+
+//??????
+// no preventDefault to get click event on ios
+event.preventDefault();
+
+ return this._onUp()
+}
+
+VirtualJoystick.prototype._onTouchMove = function(event)
+{
+ // if there is no touch in progress, do nothing
+ if( this._touchIdx === null ) return;
+
+ // try to find our touch event
+ var touchList = event.changedTouches;
+ for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++ );
+ // if touch event with the proper identifier isnt found, do nothing
+ if( i === touchList.length) return;
+ var touch = touchList[i];
+
+ event.preventDefault();
+
+ var x = touch.pageX - this._containerX;
+ var y = touch.pageY - this._containerY;
+ return this._onMove(x, y)
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// build default stickEl and baseEl //
+//////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * build the canvas for joystick base
+ */
+VirtualJoystick.prototype._buildJoystickBase = function()
+{
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = 126;
+ canvas.height = 126;
+
+ var ctx = canvas.getContext('2d');
+ ctx.beginPath();
+ ctx.strokeStyle = this._strokeStyle;
+ ctx.lineWidth = 6;
+ ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.strokeStyle = this._strokeStyle;
+ ctx.lineWidth = 2;
+ ctx.arc( canvas.width/2, canvas.width/2, 60, 0, Math.PI*2, true);
+ ctx.stroke();
+
+ return canvas;
+}
+
+/**
+ * build the canvas for joystick stick
+ */
+VirtualJoystick.prototype._buildJoystickStick = function()
+{
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = 86;
+ canvas.height = 86;
+ var ctx = canvas.getContext('2d');
+ ctx.beginPath();
+ ctx.strokeStyle = this._strokeStyle;
+ ctx.lineWidth = 6;
+ ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true);
+ ctx.stroke();
+ return canvas;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// move using translate3d method with fallback to translate > 'top' and 'left'
+// modified from https://github.com/component/translate and dependents
+//////////////////////////////////////////////////////////////////////////////////
+
+VirtualJoystick.prototype._move = function(style, x, y) // moves the top left of cavas to (x,y).
+{
+ if (this._transform) {
+ if (this._has3d) {
+ style[this._transform] = 'translate3d(' + x + 'px,' + y + 'px, 0)';
+ } else {
+ style[this._transform] = 'translate(' + x + 'px,' + y + 'px)';
+ }
+ } else {
+ style.left = x + 'px';
+ style.top = y + 'px';
+ }
+}
+
+VirtualJoystick.prototype._getTransformProperty = function()
+{
+ var styles = [
+ 'webkitTransform',
+ 'MozTransform',
+ 'msTransform',
+ 'OTransform',
+ 'transform'
+ ];
+
+ var el = document.createElement('p');
+ var style;
+
+ for (var i = 0; i < styles.length; i++) {
+ style = styles[i];
+ if (null != el.style[style]) {
+ return style;
+ }
+ }
+}
+
+VirtualJoystick.prototype._check3D = function()
+{
+ var prop = this._getTransformProperty();
+ // IE8<= doesn't have `getComputedStyle`
+ if (!prop || !window.getComputedStyle) return module.exports = false;
+
+ var map = {
+ webkitTransform: '-webkit-transform',
+ OTransform: '-o-transform',
+ msTransform: '-ms-transform',
+ MozTransform: '-moz-transform',
+ transform: 'transform'
+ };
+
+ // from: https://gist.github.com/lorenzopolidori/3794226
+ var el = document.createElement('div');
+ el.style[prop] = 'translate3d(1px,1px,1px)';
+ document.body.insertBefore(el, null);
+ var val = getComputedStyle(el).getPropertyValue(map[prop]);
+ document.body.removeChild(el);
+ var exports = null != val && val.length && 'none' != val;
+ return exports;
+}
+