diff --git a/raspberry/roberto/static/js/nipplejs.js b/raspberry/roberto/static/js/nipplejs.js
new file mode 100644
index 0000000..d404c12
--- /dev/null
+++ b/raspberry/roberto/static/js/nipplejs.js
@@ -0,0 +1,1474 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.nipplejs = f()}})(function(){var define,module,exports;
+'use strict';
+
+// Constants
+var isTouch = !!('ontouchstart' in window);
+var isPointer = window.PointerEvent ? true : false;
+var isMSPointer = window.MSPointerEvent ? true : false;
+var events = {
+ touch: {
+ start: 'touchstart',
+ move: 'touchmove',
+ end: 'touchend, touchcancel'
+ },
+ mouse: {
+ start: 'mousedown',
+ move: 'mousemove',
+ end: 'mouseup'
+ },
+ pointer: {
+ start: 'pointerdown',
+ move: 'pointermove',
+ end: 'pointerup, pointercancel'
+ },
+ MSPointer: {
+ start: 'MSPointerDown',
+ move: 'MSPointerMove',
+ end: 'MSPointerUp'
+ }
+};
+var toBind;
+var secondBind = {};
+if (isPointer) {
+ toBind = events.pointer;
+} else if (isMSPointer) {
+ toBind = events.MSPointer;
+} else if (isTouch) {
+ toBind = events.touch;
+ secondBind = events.mouse;
+} else {
+ toBind = events.mouse;
+}
+
+///////////////////////
+/// UTILS ///
+///////////////////////
+
+var u = {};
+u.distance = function (p1, p2) {
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+
+ return Math.sqrt((dx * dx) + (dy * dy));
+};
+
+u.angle = function(p1, p2) {
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+
+ return u.degrees(Math.atan2(dy, dx));
+};
+
+u.findCoord = function(p, d, a) {
+ var b = {x: 0, y: 0};
+ a = u.radians(a);
+ b.x = p.x - d * Math.cos(a);
+ b.y = p.y - d * Math.sin(a);
+ return b;
+};
+
+u.radians = function(a) {
+ return a * (Math.PI / 180);
+};
+
+u.degrees = function(a) {
+ return a * (180 / Math.PI);
+};
+
+u.bindEvt = function (el, arg, handler) {
+ var types = arg.split(/[ ,]+/g);
+ var type;
+ for (var i = 0; i < types.length; i += 1) {
+ type = types[i];
+ if (el.addEventListener) {
+ el.addEventListener(type, handler, false);
+ } else if (el.attachEvent) {
+ el.attachEvent(type, handler);
+ }
+ }
+};
+
+u.unbindEvt = function (el, arg, handler) {
+ var types = arg.split(/[ ,]+/g);
+ var type;
+ for (var i = 0; i < types.length; i += 1) {
+ type = types[i];
+ if (el.removeEventListener) {
+ el.removeEventListener(type, handler);
+ } else if (el.detachEvent) {
+ el.detachEvent(type, handler);
+ }
+ }
+};
+
+u.trigger = function (el, type, data) {
+ var evt = new CustomEvent(type, data);
+ el.dispatchEvent(evt);
+};
+
+u.prepareEvent = function (evt) {
+ evt.preventDefault();
+ return evt.type.match(/^touch/) ? evt.changedTouches : evt;
+};
+
+u.getScroll = function () {
+ var x = (window.pageXOffset !== undefined) ?
+ window.pageXOffset :
+ (document.documentElement || document.body.parentNode || document.body)
+ .scrollLeft;
+
+ var y = (window.pageYOffset !== undefined) ?
+ window.pageYOffset :
+ (document.documentElement || document.body.parentNode || document.body)
+ .scrollTop;
+ return {
+ x: x,
+ y: y
+ };
+};
+
+u.applyPosition = function (el, pos) {
+ if (pos.top || pos.right || pos.bottom || pos.left) {
+ el.style.top = pos.top;
+ el.style.right = pos.right;
+ el.style.bottom = pos.bottom;
+ el.style.left = pos.left;
+ } else {
+ el.style.left = pos.x + 'px';
+ el.style.top = pos.y + 'px';
+ }
+};
+
+u.getTransitionStyle = function (property, values, time) {
+ var obj = u.configStylePropertyObject(property);
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ if (typeof values === 'string') {
+ obj[i] = values + ' ' + time;
+ } else {
+ var st = '';
+ for (var j = 0, max = values.length; j < max; j += 1) {
+ st += values[j] + ' ' + time + ', ';
+ }
+ obj[i] = st.slice(0, -2);
+ }
+ }
+ }
+ return obj;
+};
+
+u.getVendorStyle = function (property, value) {
+ var obj = u.configStylePropertyObject(property);
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ obj[i] = value;
+ }
+ }
+ return obj;
+};
+
+u.configStylePropertyObject = function (prop) {
+ var obj = {};
+ obj[prop] = '';
+ var vendors = ['webkit', 'Moz', 'o'];
+ vendors.forEach(function (vendor) {
+ obj[vendor + prop.charAt(0).toUpperCase() + prop.slice(1)] = '';
+ });
+ return obj;
+};
+
+u.extend = function (objA, objB) {
+ for (var i in objB) {
+ if (objB.hasOwnProperty(i)) {
+ objA[i] = objB[i];
+ }
+ }
+ return objA;
+};
+
+// Overwrite only what's already present
+u.safeExtend = function (objA, objB) {
+ var obj = {};
+ for (var i in objA) {
+ if (objA.hasOwnProperty(i) && objB.hasOwnProperty(i)) {
+ obj[i] = objB[i];
+ } else if (objA.hasOwnProperty(i)) {
+ obj[i] = objA[i];
+ }
+ }
+ return obj;
+};
+
+// Map for array or unique item.
+u.map = function (ar, fn) {
+ if (ar.length) {
+ for (var i = 0, max = ar.length; i < max; i += 1) {
+ fn(ar[i]);
+ }
+ } else {
+ fn(ar);
+ }
+};
+
+///////////////////////
+/// SUPER CLASS ///
+///////////////////////
+
+function Super () {};
+
+// Basic event system.
+Super.prototype.on = function (arg, cb) {
+ var self = this;
+ var types = arg.split(/[ ,]+/g);
+ var type;
+ self._handlers_ = self._handlers_ || {};
+
+ for (var i = 0; i < types.length; i += 1) {
+ type = types[i];
+ self._handlers_[type] = self._handlers_[type] || [];
+ self._handlers_[type].push(cb);
+ }
+ return self;
+};
+
+Super.prototype.off = function (type, cb) {
+ var self = this;
+ self._handlers_ = self._handlers_ || {};
+
+ if (type === undefined) {
+ self._handlers_ = {};
+ } else if (cb === undefined) {
+ self._handlers_[type] = null;
+ } else if (self._handlers_[type] &&
+ self._handlers_[type].indexOf(cb) >= 0) {
+ self._handlers_[type].splice(self._handlers_[type].indexOf(cb), 1);
+ }
+
+ return self;
+};
+
+Super.prototype.trigger = function (arg, data) {
+ var self = this;
+ var types = arg.split(/[ ,]+/g);
+ var type;
+ self._handlers_ = self._handlers_ || {};
+
+ for (var i = 0; i < types.length; i += 1) {
+ type = types[i];
+ if (self._handlers_[type] && self._handlers_[type].length) {
+ self._handlers_[type].forEach(function (handler) {
+ handler.call(self, {
+ type: type,
+ target: self
+ }, data);
+ });
+ }
+ }
+};
+
+// Configuration
+Super.prototype.config = function (options) {
+ var self = this;
+ self.options = self.defaults || {};
+ if (options) {
+ self.options = u.safeExtend(self.options, options);
+ }
+};
+
+// Bind internal events.
+Super.prototype.bindEvt = function (el, type) {
+ var self = this;
+ self._domHandlers_ = self._domHandlers_ || {};
+
+ self._domHandlers_[type] = function () {
+ if (typeof self['on' + type] === 'function') {
+ self['on' + type].apply(self, arguments);
+ } else {
+ console.warn('[WARNING] : Missing "on' + type + '" handler.');
+ }
+ };
+
+ u.bindEvt(el, toBind[type], self._domHandlers_[type]);
+
+ if (secondBind[type]) {
+ // Support for both touch and mouse at the same time.
+ u.bindEvt(el, secondBind[type], self._domHandlers_[type]);
+ }
+
+ return self;
+};
+
+// Unbind dom events.
+Super.prototype.unbindEvt = function (el, type) {
+ var self = this;
+ self._domHandlers_ = self._domHandlers_ || {};
+
+ u.unbindEvt(el, toBind[type], self._domHandlers_[type]);
+
+ if (secondBind[type]) {
+ // Support for both touch and mouse at the same time.
+ u.unbindEvt(el, secondBind[type], self._domHandlers_[type]);
+ }
+
+ delete self._domHandlers_[type];
+
+ return this;
+};
+
+///////////////////////
+/// THE NIPPLE ///
+///////////////////////
+
+function Nipple (collection, options) {
+ this.identifier = options.identifier;
+ this.position = options.position;
+ this.frontPosition = options.frontPosition;
+ this.collection = collection;
+
+ // Defaults
+ this.defaults = {
+ size: 100,
+ threshold: 0.1,
+ color: 'white',
+ fadeTime: 250,
+ dataOnly: false,
+ restJoystick: true,
+ restOpacity: 0.5,
+ mode: 'dynamic',
+ zone: document.body,
+ lockX: false,
+ lockY: false
+ };
+
+ this.config(options);
+
+ // Overwrites
+ if (this.options.mode === 'dynamic') {
+ this.options.restOpacity = 0;
+ }
+
+ this.id = Nipple.id;
+ Nipple.id += 1;
+ this.buildEl()
+ .stylize();
+
+ // Nipple's API.
+ this.instance = {
+ el: this.ui.el,
+ on: this.on.bind(this),
+ off: this.off.bind(this),
+ show: this.show.bind(this),
+ hide: this.hide.bind(this),
+ add: this.addToDom.bind(this),
+ remove: this.removeFromDom.bind(this),
+ destroy: this.destroy.bind(this),
+ resetDirection: this.resetDirection.bind(this),
+ computeDirection: this.computeDirection.bind(this),
+ trigger: this.trigger.bind(this),
+ position: this.position,
+ frontPosition: this.frontPosition,
+ ui: this.ui,
+ identifier: this.identifier,
+ id: this.id,
+ options: this.options
+ };
+
+ return this.instance;
+};
+
+Nipple.prototype = new Super();
+Nipple.constructor = Nipple;
+Nipple.id = 0;
+
+// Build the dom element of the Nipple instance.
+Nipple.prototype.buildEl = function (options) {
+ this.ui = {};
+
+ if (this.options.dataOnly) {
+ return this;
+ }
+
+ this.ui.el = document.createElement('div');
+ this.ui.back = document.createElement('div');
+ this.ui.front = document.createElement('div');
+
+ this.ui.el.className = 'nipple collection_' + this.collection.id;
+ this.ui.back.className = 'back';
+ this.ui.front.className = 'front';
+
+ this.ui.el.setAttribute('id', 'nipple_' + this.collection.id +
+ '_' + this.id);
+
+ this.ui.el.appendChild(this.ui.back);
+ this.ui.el.appendChild(this.ui.front);
+
+ return this;
+};
+
+// Apply CSS to the Nipple instance.
+Nipple.prototype.stylize = function () {
+ if (this.options.dataOnly) {
+ return this;
+ }
+ var animTime = this.options.fadeTime + 'ms';
+ var borderStyle = u.getVendorStyle('borderRadius', '50%');
+ var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime);
+ var styles = {};
+ styles.el = {
+ position: 'absolute',
+ opacity: this.options.restOpacity,
+ display: 'block',
+ 'zIndex': 999
+ };
+
+ styles.back = {
+ position: 'absolute',
+ display: 'block',
+ width: this.options.size + 'px',
+ height: this.options.size + 'px',
+ marginLeft: -this.options.size / 2 + 'px',
+ marginTop: -this.options.size / 2 + 'px',
+ background: this.options.color,
+ 'opacity': '.5'
+ };
+
+ styles.front = {
+ width: this.options.size / 2 + 'px',
+ height: this.options.size / 2 + 'px',
+ position: 'absolute',
+ display: 'block',
+ marginLeft: -this.options.size / 4 + 'px',
+ marginTop: -this.options.size / 4 + 'px',
+ background: this.options.color,
+ 'opacity': '.5'
+ };
+
+ u.extend(styles.el, transitStyle);
+ u.extend(styles.back, borderStyle);
+ u.extend(styles.front, borderStyle);
+
+ this.applyStyles(styles);
+
+ return this;
+};
+
+Nipple.prototype.applyStyles = function (styles) {
+ // Apply styles
+ for (var i in this.ui) {
+ if (this.ui.hasOwnProperty(i)) {
+ for (var j in styles[i]) {
+ this.ui[i].style[j] = styles[i][j];
+ }
+ }
+ }
+
+ return this;
+};
+
+// Inject the Nipple instance into DOM.
+Nipple.prototype.addToDom = function () {
+ // We're not adding it if we're dataOnly or already in dom.
+ if (this.options.dataOnly || document.body.contains(this.ui.el)) {
+ return this;
+ }
+ this.options.zone.appendChild(this.ui.el);
+ return this;
+};
+
+// Remove the Nipple instance from DOM.
+Nipple.prototype.removeFromDom = function () {
+ if (this.options.dataOnly || !document.body.contains(this.ui.el)) {
+ return this;
+ }
+ this.options.zone.removeChild(this.ui.el);
+ return this;
+};
+
+// Entirely destroy this nipple
+Nipple.prototype.destroy = function () {
+ clearTimeout(this.removeTimeout);
+ clearTimeout(this.showTimeout);
+ clearTimeout(this.restTimeout);
+ this.trigger('destroyed', this.instance);
+ this.removeFromDom();
+ this.off();
+};
+
+// Fade in the Nipple instance.
+Nipple.prototype.show = function (cb) {
+ var self = this;
+
+ if (self.options.dataOnly) {
+ return self;
+ }
+
+ clearTimeout(self.removeTimeout);
+ clearTimeout(self.showTimeout);
+ clearTimeout(self.restTimeout);
+
+ self.addToDom();
+
+ self.restCallback();
+
+ setTimeout(function () {
+ self.ui.el.style.opacity = 1;
+ }, 0);
+
+ self.showTimeout = setTimeout(function () {
+ self.trigger('shown', self.instance);
+ if (typeof cb === 'function') {
+ cb.call(this);
+ }
+ }, self.options.fadeTime);
+
+ return self;
+};
+
+// Fade out the Nipple instance.
+Nipple.prototype.hide = function (cb) {
+ var self = this;
+
+ if (self.options.dataOnly) {
+ return self;
+ }
+
+ self.ui.el.style.opacity = self.options.restOpacity;
+
+ clearTimeout(self.removeTimeout);
+ clearTimeout(self.showTimeout);
+ clearTimeout(self.restTimeout);
+
+ self.removeTimeout = setTimeout(
+ function () {
+ var display = self.options.mode === 'dynamic' ? 'none' : 'block';
+ self.ui.el.style.display = display;
+ if (typeof cb === 'function') {
+ cb.call(self);
+ }
+
+ self.trigger('hidden', self.instance);
+ },
+ self.options.fadeTime
+ );
+ if (self.options.restJoystick) {
+ self.restPosition();
+ }
+
+ return self;
+};
+
+Nipple.prototype.restPosition = function (cb) {
+ var self = this;
+ self.frontPosition = {
+ x: 0,
+ y: 0
+ };
+ var animTime = self.options.fadeTime + 'ms';
+
+ var transitStyle = {};
+ transitStyle.front = u.getTransitionStyle('transition',
+ ['top', 'left'], animTime);
+
+ var styles = {front: {}};
+ styles.front = {
+ left: self.frontPosition.x + 'px',
+ top: self.frontPosition.y + 'px'
+ };
+
+ self.applyStyles(transitStyle);
+ self.applyStyles(styles);
+
+ self.restTimeout = setTimeout(
+ function () {
+ if (typeof cb === 'function') {
+ cb.call(self);
+ }
+ self.restCallback();
+ },
+ self.options.fadeTime
+ );
+};
+
+Nipple.prototype.restCallback = function () {
+ var self = this;
+ var transitStyle = {};
+ transitStyle.front = u.getTransitionStyle('transition', 'none', '');
+ self.applyStyles(transitStyle);
+ self.trigger('rested', self.instance);
+};
+
+Nipple.prototype.resetDirection = function () {
+ // Fully rebuild the object to let the iteration possible.
+ this.direction = {
+ x: false,
+ y: false,
+ angle: false
+ };
+};
+
+Nipple.prototype.computeDirection = function (obj) {
+ var rAngle = obj.angle.radian;
+ var angle45 = Math.PI / 4;
+ var angle90 = Math.PI / 2;
+ var direction, directionX, directionY;
+
+ // Angular direction
+ // \ UP /
+ // \ /
+ // LEFT RIGHT
+ // / \
+ // /DOWN \
+ //
+ if (
+ rAngle > angle45 &&
+ rAngle < (angle45 * 3) &&
+ !obj.lockX
+ ) {
+ direction = 'up';
+ } else if (
+ rAngle > -angle45 &&
+ rAngle <= angle45 &&
+ !obj.lockY
+ ) {
+ direction = 'left';
+ } else if (
+ rAngle > (-angle45 * 3) &&
+ rAngle <= -angle45 &&
+ !obj.lockX
+ ) {
+ direction = 'down';
+ } else if (!obj.lockY) {
+ direction = 'right';
+ }
+
+ // Plain direction
+ // UP |
+ // _______ | RIGHT
+ // LEFT |
+ // DOWN |
+ if (!obj.lockY) {
+ if (rAngle > -angle90 && rAngle < angle90) {
+ directionX = 'left';
+ } else {
+ directionX = 'right';
+ }
+ }
+
+ if (!obj.lockX) {
+ if (rAngle > 0) {
+ directionY = 'up';
+ } else {
+ directionY = 'down';
+ }
+ }
+
+ if (obj.force > this.options.threshold) {
+ var oldDirection = {};
+ for (var i in this.direction) {
+ if (this.direction.hasOwnProperty(i)) {
+ oldDirection[i] = this.direction[i];
+ }
+ }
+
+ var same = {};
+
+ this.direction = {
+ x: directionX,
+ y: directionY,
+ angle: direction
+ };
+
+ obj.direction = this.direction;
+
+ for (var i in oldDirection) {
+ if (oldDirection[i] === this.direction[i]) {
+ same[i] = true;
+ }
+ }
+
+ // If all 3 directions are the same, we don't trigger anything.
+ if (same.x && same.y && same.angle) {
+ return obj;
+ }
+
+ if (!same.x || !same.y) {
+ this.trigger('plain', obj);
+ }
+
+ if (!same.x) {
+ this.trigger('plain:' + directionX, obj);
+ }
+
+ if (!same.y) {
+ this.trigger('plain:' + directionY, obj);
+ }
+
+ if (!same.angle) {
+ this.trigger('dir dir:' + direction, obj);
+ }
+ }
+ return obj;
+};
+
+/* global Nipple, Super */
+
+///////////////////////////
+/// THE COLLECTION ///
+///////////////////////////
+
+function Collection (manager, options) {
+ var self = this;
+ self.nipples = [];
+ self.idles = [];
+ self.actives = [];
+ self.ids = [];
+ self.pressureIntervals = {};
+ self.manager = manager;
+ self.id = Collection.id;
+ Collection.id += 1;
+
+ // Defaults
+ self.defaults = {
+ zone: document.body,
+ multitouch: false,
+ maxNumberOfNipples: 10,
+ mode: 'dynamic',
+ position: {top: 0, left: 0},
+ catchDistance: 200,
+ size: 100,
+ threshold: 0.1,
+ color: 'white',
+ fadeTime: 250,
+ dataOnly: false,
+ restJoystick: true,
+ restOpacity: 0.5,
+ lockX: false,
+ lockY: false
+ };
+
+ self.config(options);
+
+ // Overwrites
+ if (self.options.mode === 'static' || self.options.mode === 'semi') {
+ self.options.multitouch = false;
+ }
+
+ if (!self.options.multitouch) {
+ self.options.maxNumberOfNipples = 1;
+ }
+
+ self.updateBox();
+ self.prepareNipples();
+ self.bindings();
+ self.begin();
+
+ return self.nipples;
+}
+
+Collection.prototype = new Super();
+Collection.constructor = Collection;
+Collection.id = 0;
+
+Collection.prototype.prepareNipples = function () {
+ var self = this;
+ var nips = self.nipples;
+
+ // Public API Preparation.
+ nips.on = self.on.bind(self);
+ nips.off = self.off.bind(self);
+ nips.options = self.options;
+ nips.destroy = self.destroy.bind(self);
+ nips.ids = self.ids;
+ nips.id = self.id;
+ nips.processOnMove = self.processOnMove.bind(self);
+ nips.processOnEnd = self.processOnEnd.bind(self);
+ nips.get = function (id) {
+ if (id === undefined) {
+ return nips[0];
+ }
+ for (var i = 0, max = nips.length; i < max; i += 1) {
+ if (nips[i].identifier === id) {
+ return nips[i];
+ }
+ }
+ return false;
+ };
+};
+
+Collection.prototype.bindings = function () {
+ var self = this;
+ // Touch start event.
+ self.bindEvt(self.options.zone, 'start');
+ // Avoid native touch actions (scroll, zoom etc...) on the zone.
+ self.options.zone.style.touchAction = 'none';
+ self.options.zone.style.msTouchAction = 'none';
+};
+
+Collection.prototype.begin = function () {
+ var self = this;
+ var opts = self.options;
+
+ // We place our static nipple
+ // if needed.
+ if (opts.mode === 'static') {
+ var nipple = self.createNipple(
+ opts.position,
+ self.manager.getIdentifier()
+ );
+ // Add it to the dom.
+ nipple.add();
+ // Store it in idles.
+ self.idles.push(nipple);
+ }
+};
+
+// Nipple Factory
+Collection.prototype.createNipple = function (position, identifier) {
+ var self = this;
+ var scroll = u.getScroll();
+ var toPutOn = {};
+ var opts = self.options;
+
+ if (position.x && position.y) {
+ toPutOn = {
+ x: position.x -
+ (scroll.x + self.box.left),
+ y: position.y -
+ (scroll.y + self.box.top)
+ };
+ } else if (
+ position.top ||
+ position.right ||
+ position.bottom ||
+ position.left
+ ) {
+
+ // We need to compute the position X / Y of the joystick.
+ var dumb = document.createElement('DIV');
+ dumb.style.display = 'hidden';
+ dumb.style.top = position.top;
+ dumb.style.right = position.right;
+ dumb.style.bottom = position.bottom;
+ dumb.style.left = position.left;
+ dumb.style.position = 'absolute';
+
+ opts.zone.appendChild(dumb);
+ var dumbBox = dumb.getBoundingClientRect();
+ opts.zone.removeChild(dumb);
+
+ toPutOn = position;
+ position = {
+ x: dumbBox.left + scroll.x,
+ y: dumbBox.top + scroll.y
+ };
+ }
+
+ var nipple = new Nipple(self, {
+ color: opts.color,
+ size: opts.size,
+ threshold: opts.threshold,
+ fadeTime: opts.fadeTime,
+ dataOnly: opts.dataOnly,
+ restJoystick: opts.restJoystick,
+ restOpacity: opts.restOpacity,
+ mode: opts.mode,
+ identifier: identifier,
+ position: position,
+ zone: opts.zone,
+ frontPosition: {
+ x: 0,
+ y: 0
+ }
+ });
+
+ if (!opts.dataOnly) {
+ u.applyPosition(nipple.ui.el, toPutOn);
+ u.applyPosition(nipple.ui.front, nipple.frontPosition);
+ }
+ self.nipples.push(nipple);
+ self.trigger('added ' + nipple.identifier + ':added', nipple);
+ self.manager.trigger('added ' + nipple.identifier + ':added', nipple);
+
+ self.bindNipple(nipple);
+
+ return nipple;
+};
+
+Collection.prototype.updateBox = function () {
+ var self = this;
+ self.box = self.options.zone.getBoundingClientRect();
+};
+
+Collection.prototype.bindNipple = function (nipple) {
+ var self = this;
+ var type;
+ // Bubble up identified events.
+ var handler = function (evt, data) {
+ // Identify the event type with the nipple's id.
+ type = evt.type + ' ' + data.id + ':' + evt.type;
+ self.trigger(type, data);
+ };
+
+ // When it gets destroyed.
+ nipple.on('destroyed', self.onDestroyed.bind(self));
+
+ // Other events that will get bubbled up.
+ nipple.on('shown hidden rested dir plain', handler);
+ nipple.on('dir:up dir:right dir:down dir:left', handler);
+ nipple.on('plain:up plain:right plain:down plain:left', handler);
+};
+
+Collection.prototype.pressureFn = function (touch, nipple, identifier) {
+ var self = this;
+ var previousPressure = 0;
+ clearInterval(self.pressureIntervals[identifier]);
+ // Create an interval that will read the pressure every 100ms
+ self.pressureIntervals[identifier] = setInterval(function () {
+ var pressure = touch.force || touch.pressure ||
+ touch.webkitForce || 0;
+ if (pressure !== previousPressure) {
+ nipple.trigger('pressure', pressure);
+ self.trigger('pressure ' +
+ nipple.identifier + ':pressure', pressure);
+ previousPressure = pressure;
+ }
+ }.bind(self), 100);
+};
+
+Collection.prototype.onstart = function (evt) {
+ var self = this;
+ var opts = self.options;
+ evt = u.prepareEvent(evt);
+
+ // Update the box position
+ self.updateBox();
+
+ var process = function (touch) {
+ // If we can create new nipples
+ // meaning we don't have more active nipples than we should.
+ if (self.actives.length < opts.maxNumberOfNipples) {
+ self.processOnStart(touch);
+ }
+ };
+
+ u.map(evt, process);
+
+ // We ask upstream to bind the document
+ // on 'move' and 'end'
+ self.manager.bindDocument();
+ return false;
+};
+
+Collection.prototype.processOnStart = function (evt) {
+ var self = this;
+ var opts = self.options;
+ var indexInIdles;
+ var identifier = self.manager.getIdentifier(evt);
+ var pressure = evt.force || evt.pressure || evt.webkitForce || 0;
+ var position = {
+ x: evt.pageX,
+ y: evt.pageY
+ };
+
+ var nipple = self.getOrCreate(identifier, position);
+
+ // Update its touch identifier
+ if (nipple.identifier !== identifier) {
+ self.manager.removeIdentifier(nipple.identifier);
+ }
+ nipple.identifier = identifier;
+
+ var process = function (nip) {
+ // Trigger the start.
+ nip.trigger('start', nip);
+ self.trigger('start ' + nip.id + ':start', nip);
+
+ nip.show();
+ if (pressure > 0) {
+ self.pressureFn(evt, nip, nip.identifier);
+ }
+ // Trigger the first move event.
+ self.processOnMove(evt);
+ };
+
+ // Transfer it from idles to actives.
+ if ((indexInIdles = self.idles.indexOf(nipple)) >= 0) {
+ self.idles.splice(indexInIdles, 1);
+ }
+
+ // Store the nipple in the actives array
+ self.actives.push(nipple);
+ self.ids.push(nipple.identifier);
+
+ if (opts.mode !== 'semi') {
+ process(nipple);
+ } else {
+ // In semi we check the distance of the touch
+ // to decide if we have to reset the nipple
+ var distance = u.distance(position, nipple.position);
+ if (distance <= opts.catchDistance) {
+ process(nipple);
+ } else {
+ nipple.destroy();
+ self.processOnStart(evt);
+ return;
+ }
+ }
+
+ return nipple;
+};
+
+Collection.prototype.getOrCreate = function (identifier, position) {
+ var self = this;
+ var opts = self.options;
+ var nipple;
+
+ // If we're in static or semi, we might already have an active.
+ if (/(semi|static)/.test(opts.mode)) {
+ // Get the active one.
+ // TODO: Multi-touche for semi and static will start here.
+ // Return the nearest one.
+ nipple = self.idles[0];
+ if (nipple) {
+ self.idles.splice(0, 1);
+ return nipple;
+ }
+
+ if (opts.mode === 'semi') {
+ // If we're in semi mode, we need to create one.
+ return self.createNipple(position, identifier);
+ }
+
+ console.warn('Coudln\'t find the needed nipple.');
+ return false;
+ }
+ // In dynamic, we create a new one.
+ nipple = self.createNipple(position, identifier);
+ return nipple;
+};
+
+Collection.prototype.processOnMove = function (evt) {
+ var self = this;
+ var opts = self.options;
+ var identifier = self.manager.getIdentifier(evt);
+ var nipple = self.nipples.get(identifier);
+
+ if (!nipple) {
+ // This is here just for safety.
+ // It shouldn't happen.
+ console.error('Found zombie joystick with ID ' + identifier);
+ self.manager.removeIdentifier(identifier);
+ return;
+ }
+
+ nipple.identifier = identifier;
+
+ var size = nipple.options.size / 2;
+ var pos = {
+ x: evt.pageX,
+ y: evt.pageY
+ };
+
+ var dist = u.distance(pos, nipple.position);
+ var angle = u.angle(pos, nipple.position);
+ var rAngle = u.radians(angle);
+ var force = dist / size;
+
+ // If distance is bigger than nipple's size
+ // we clamp the position.
+ if (dist > size) {
+ dist = size;
+ pos = u.findCoord(nipple.position, dist, angle);
+ }
+
+ var xPosition = pos.x - nipple.position.x
+ var yPosition = pos.y - nipple.position.y
+
+ if (opts.lockX){
+ yPosition = 0
+ }
+ if (opts.lockY) {
+ xPosition = 0
+ }
+
+ nipple.frontPosition = {
+ x: xPosition,
+ y: yPosition
+ };
+
+ if (!opts.dataOnly) {
+ u.applyPosition(nipple.ui.front, nipple.frontPosition);
+ }
+
+ // Prepare event's datas.
+ var toSend = {
+ identifier: nipple.identifier,
+ position: pos,
+ force: force,
+ pressure: evt.force || evt.pressure || evt.webkitForce || 0,
+ distance: dist,
+ angle: {
+ radian: rAngle,
+ degree: angle
+ },
+ instance: nipple,
+ lockX: opts.lockX,
+ lockY: opts.lockY
+ };
+
+ // Compute the direction's datas.
+ toSend = nipple.computeDirection(toSend);
+
+ // Offset angles to follow units circle.
+ toSend.angle = {
+ radian: u.radians(180 - angle),
+ degree: 180 - angle
+ };
+
+ // Send everything to everyone.
+ nipple.trigger('move', toSend);
+ self.trigger('move ' + nipple.id + ':move', toSend);
+};
+
+Collection.prototype.processOnEnd = function (evt) {
+ var self = this;
+ var opts = self.options;
+ var identifier = self.manager.getIdentifier(evt);
+ var nipple = self.nipples.get(identifier);
+ var removedIdentifier = self.manager.removeIdentifier(nipple.identifier);
+
+ if (!nipple) {
+ return;
+ }
+
+ if (!opts.dataOnly) {
+ nipple.hide(function () {
+ if (opts.mode === 'dynamic') {
+ nipple.trigger('removed', nipple);
+ self.trigger('removed ' + nipple.id + ':removed', nipple);
+ self.manager
+ .trigger('removed ' + nipple.id + ':removed', nipple);
+ nipple.destroy();
+ }
+ });
+ }
+
+ // Clear the pressure interval reader
+ clearInterval(self.pressureIntervals[nipple.identifier]);
+
+ // Reset the direciton of the nipple, to be able to trigger a new direction
+ // on start.
+ nipple.resetDirection();
+
+ nipple.trigger('end', nipple);
+ self.trigger('end ' + nipple.id + ':end', nipple);
+
+ // Remove identifier from our bank.
+ if (self.ids.indexOf(nipple.identifier) >= 0) {
+ self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
+ }
+
+ // Clean our actives array.
+ if (self.actives.indexOf(nipple) >= 0) {
+ self.actives.splice(self.actives.indexOf(nipple), 1);
+ }
+
+ if (/(semi|static)/.test(opts.mode)) {
+ // Transfer nipple from actives to idles
+ // if we're in semi or static mode.
+ self.idles.push(nipple);
+ } else if (self.nipples.indexOf(nipple) >= 0) {
+ // Only if we're not in semi or static mode
+ // we can remove the instance.
+ self.nipples.splice(self.nipples.indexOf(nipple), 1);
+ }
+
+ // We unbind move and end.
+ self.manager.unbindDocument();
+
+ // We add back the identifier of the idle nipple;
+ if (/(semi|static)/.test(opts.mode)) {
+ self.manager.ids[removedIdentifier.id] = removedIdentifier.identifier;
+ }
+};
+
+// Remove destroyed nipple from the lists
+Collection.prototype.onDestroyed = function(evt, nipple) {
+ var self = this;
+ if (self.nipples.indexOf(nipple) >= 0) {
+ self.nipples.splice(self.nipples.indexOf(nipple), 1);
+ }
+ if (self.actives.indexOf(nipple) >= 0) {
+ self.actives.splice(self.actives.indexOf(nipple), 1);
+ }
+ if (self.idles.indexOf(nipple) >= 0) {
+ self.idles.splice(self.idles.indexOf(nipple), 1);
+ }
+ if (self.ids.indexOf(nipple.identifier) >= 0) {
+ self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
+ }
+
+ // Remove the identifier from our bank
+ self.manager.removeIdentifier(nipple.identifier);
+
+ // We unbind move and end.
+ self.manager.unbindDocument();
+};
+
+// Cleanly destroy the manager
+Collection.prototype.destroy = function () {
+ var self = this;
+ self.unbindEvt(self.options.zone, 'start');
+
+ // Destroy nipples.
+ self.nipples.forEach(function(nipple) {
+ nipple.destroy();
+ });
+
+ // Clean 3DTouch intervals.
+ for (var i in self.pressureIntervals) {
+ if (self.pressureIntervals.hasOwnProperty(i)) {
+ clearInterval(self.pressureIntervals[i]);
+ }
+ }
+
+ // Notify the manager passing the instance
+ self.trigger('destroyed', self.nipples);
+ // We unbind move and end.
+ self.manager.unbindDocument();
+ // Unbind everything.
+ self.off();
+};
+
+/* global u, Super, Collection */
+
+///////////////////////
+/// MANAGER ///
+///////////////////////
+
+function Manager (options) {
+ var self = this;
+ self.ids = {};
+ self.index = 0;
+ self.collections = [];
+
+ self.config(options);
+ self.prepareCollections();
+
+ // Listen for resize, to reposition every joysticks
+ var resizeTimer;
+ u.bindEvt(window, 'resize', function (evt) {
+ clearTimeout(resizeTimer);
+ resizeTimer = setTimeout(function () {
+ var pos;
+ var scroll = u.getScroll();
+ self.collections.forEach(function (collection) {
+ collection.forEach(function (nipple) {
+ pos = nipple.el.getBoundingClientRect();
+ nipple.position = {
+ x: scroll.x + pos.left,
+ y: scroll.y + pos.top
+ };
+ });
+ });
+ }, 100);
+ });
+
+ return self.collections;
+};
+
+Manager.prototype = new Super();
+Manager.constructor = Manager;
+
+Manager.prototype.prepareCollections = function () {
+ var self = this;
+ // Public API Preparation.
+ self.collections.create = self.create.bind(self);
+ // Listen to anything
+ self.collections.on = self.on.bind(self);
+ // Unbind general events
+ self.collections.off = self.off.bind(self);
+ // Destroy everything
+ self.collections.destroy = self.destroy.bind(self);
+ // Get any nipple
+ self.collections.get = function (id) {
+ var nipple;
+ self.collections.every(function (collection) {
+ if (nipple = collection.get(id)) {
+ return false;
+ }
+ return true;
+ });
+ return nipple;
+ };
+};
+
+Manager.prototype.create = function (options) {
+ return this.createCollection(options);
+};
+
+// Collection Factory
+Manager.prototype.createCollection = function (options) {
+ var self = this;
+ var collection = new Collection(self, options);
+
+ self.bindCollection(collection);
+ self.collections.push(collection);
+
+ return collection;
+};
+
+Manager.prototype.bindCollection = function (collection) {
+ var self = this;
+ var type;
+ // Bubble up identified events.
+ var handler = function (evt, data) {
+ // Identify the event type with the nipple's identifier.
+ type = evt.type + ' ' + data.id + ':' + evt.type;
+ self.trigger(type, data);
+ };
+
+ // When it gets destroyed we clean.
+ collection.on('destroyed', self.onDestroyed.bind(self));
+
+ // Other events that will get bubbled up.
+ collection.on('shown hidden rested dir plain', handler);
+ collection.on('dir:up dir:right dir:down dir:left', handler);
+ collection.on('plain:up plain:right plain:down plain:left', handler);
+};
+
+Manager.prototype.bindDocument = function () {
+ var self = this;
+ // Bind only if not already binded
+ if (!self.binded) {
+ self.bindEvt(document, 'move')
+ .bindEvt(document, 'end');
+ self.binded = true;
+ }
+};
+
+Manager.prototype.unbindDocument = function (force) {
+ var self = this;
+ // If there are no touch left
+ // unbind the document.
+ if (!Object.keys(self.ids).length || force === true) {
+ self.unbindEvt(document, 'move')
+ .unbindEvt(document, 'end');
+ self.binded = false;
+ }
+};
+
+Manager.prototype.getIdentifier = function (evt) {
+ var id;
+ // If no event, simple increment
+ if (!evt) {
+ id = this.index;
+ } else {
+ // Extract identifier from event object.
+ // Unavailable in mouse events so replaced by latest increment.
+ id = evt.identifier === undefined ? evt.pointerId : evt.identifier;
+ if (id === undefined) {
+ id = this.latest || 0;
+ }
+ }
+
+ if (this.ids[id] === undefined) {
+ this.ids[id] = this.index;
+ this.index += 1;
+ }
+
+ // Keep the latest id used in case we're using an unidentified mouseEvent
+ this.latest = id;
+ return this.ids[id];
+};
+
+Manager.prototype.removeIdentifier = function (identifier) {
+ var removed = {};
+ for (var id in this.ids) {
+ if (this.ids[id] === identifier) {
+ removed.id = id;
+ removed.identifier = this.ids[id];
+ delete this.ids[id];
+ break;
+ }
+ }
+ return removed;
+};
+
+Manager.prototype.onmove = function (evt) {
+ var self = this;
+ self.onAny('move', evt);
+ return false;
+};
+
+Manager.prototype.onend = function (evt) {
+ var self = this;
+ self.onAny('end', evt);
+ return false;
+};
+
+Manager.prototype.oncancel = function (evt) {
+ var self = this;
+ self.onAny('end', evt);
+ return false;
+};
+
+Manager.prototype.onAny = function (which, evt) {
+ var self = this;
+ var id;
+ var processFn = 'processOn' + which.charAt(0).toUpperCase() +
+ which.slice(1);
+ evt = u.prepareEvent(evt);
+ var processColl = function (e, id, coll) {
+ if (coll.ids.indexOf(id) >= 0) {
+ coll[processFn](e);
+ // Mark the event to avoid cleaning it later.
+ e._found_ = true;
+ }
+ };
+ var processEvt = function (e) {
+ id = self.getIdentifier(e);
+ u.map(self.collections, processColl.bind(null, e, id));
+ // If the event isn't handled by any collection,
+ // we need to clean its identifier.
+ if (!e._found_) {
+ self.removeIdentifier(id);
+ }
+ };
+
+ u.map(evt, processEvt);
+
+ return false;
+};
+
+// Cleanly destroy the manager
+Manager.prototype.destroy = function () {
+ var self = this;
+ self.unbindDocument(true);
+ self.ids = {};
+ self.index = 0;
+ self.collections.forEach(function(collection) {
+ collection.destroy();
+ });
+ self.off();
+};
+
+// When a collection gets destroyed
+// we clean behind.
+Manager.prototype.onDestroyed = function (evt, coll) {
+ var self = this;
+ if (self.collections.indexOf(coll) < 0) {
+ return false;
+ }
+ self.collections.splice(self.collections.indexOf(coll), 1);
+};
+
+var factory = new Manager();
+return {
+ create: function (options) {
+ return factory.create(options);
+ },
+ factory: factory
+};
+
+});
diff --git a/raspberry/roberto/views/frontend/templates/gamepad.js b/raspberry/roberto/views/frontend/templates/gamepad.js
index 5f3afd0..27a2068 100644
--- a/raspberry/roberto/views/frontend/templates/gamepad.js
+++ b/raspberry/roberto/views/frontend/templates/gamepad.js
@@ -95,56 +95,13 @@ if (haveEvents) {
}
// 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
- });
+ var joystick = nipplejs.create({
+ zone: document.getElementById('controls'),
+ color: 'blue'
+ });
/*
- joystick2.addEventListener('touchStart', function(){
- console.log('fire')
- })
-*/
-
setInterval(function(){
- if (joystick && joystick2) {
+ if (joystick) {
if (joystick._pressed === true || joystick2._pressed === true) {
var dx = joystick.deltaX() / joystick._stickRadius;
var dy = joystick.deltaY() / joystick._stickRadius;
@@ -154,8 +111,8 @@ setInterval(function(){
}
}
}, 50);
-
+*/
window.addEventListener("orientationchange", function(event) {
- joystick._updatePositionOfContainer();
- joystick2._updatePositionOfContainer();
+ //joystick._updatePositionOfContainer();
+ //joystick2._updatePositionOfContainer();
});
diff --git a/raspberry/roberto/views/frontend/templates/index.html b/raspberry/roberto/views/frontend/templates/index.html
index 3ea082d..df044f2 100644
--- a/raspberry/roberto/views/frontend/templates/index.html
+++ b/raspberry/roberto/views/frontend/templates/index.html
@@ -76,6 +76,7 @@
+
-