| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| require('es6-shim'); |
| |
| var $ = require('./util/jquery'); |
| var defineClass = require('./util/define-class'); |
| |
| var Multimap = require('multimap'); |
| |
| var DeviceSync = require('./sync-util/device-sync'); |
| |
| /** |
| * TODO(rosswang): The future of this helper method is a bit unclear given that |
| * someday we will want to support actual vectors. |
| */ |
| function getGestureDirection(v) { |
| if (v.y < 0 && -v.y > Math.abs(v.x)) { |
| return DeviceSync.UP; |
| } |
| if (v.y > 0 && v.y > Math.abs(v.x)) { |
| return DeviceSync.DOWN; |
| } |
| if (v.x < 0) { |
| return DeviceSync.LEFT; |
| } |
| if (v.x > 0) { |
| return DeviceSync.RIGHT; |
| } |
| } |
| |
| /** |
| * Multimap union. |
| */ |
| function union(a, b) { |
| var result = new Multimap(); |
| function add(value, key) { |
| if (!result.has(key, value)) { |
| result.set(key, value); |
| } |
| } |
| a.forEach(add); |
| b.forEach(add); |
| return result; |
| } |
| |
| /** |
| * Multimap difference. |
| */ |
| function difference(u, c) { |
| var result = new Multimap(); |
| u.forEach(function(value, key) { |
| if (!c.has(key, value)) { |
| result.set(key, value); |
| } |
| }); |
| return result; |
| } |
| |
| var CastingManager = defineClass({ |
| publics: { |
| /* TODO(rosswang): For now, let's do two-button click and drag as the |
| * casting gesture. Eventually, we'll want to evaluate and support others. |
| */ |
| makeCastable: function($handle, opts) { |
| var self = this; |
| |
| var castHandler = { |
| buttons: 0 |
| }; |
| |
| $handle.mousedown(function(e) { |
| castHandler.buttons |= 1 << (e.which - 1); |
| if (castHandler.buttons === 5 || castHandler.buttons === 2) { |
| castHandler.origin = { |
| x: e.pageX, |
| y: e.pageY |
| }; |
| } |
| }); |
| |
| function processMouseUpdate(e) { |
| if (castHandler.buttons === 0 && castHandler.origin) { |
| self.interpretCastVector({ |
| x: e.pageX - castHandler.origin.x, |
| y: e.pageY - castHandler.origin.y |
| }, opts.spec); |
| delete castHandler.origin; |
| } |
| } |
| |
| $(global.document).mousemove(function(e) { |
| if (e.which === 0) { |
| castHandler.buttons = 0; |
| processMouseUpdate(e); |
| } |
| }).mouseup(function(e) { |
| castHandler.buttons &= ~(1 << (e.which - 1)); |
| processMouseUpdate(e); |
| }); |
| } |
| }, |
| |
| privates: { |
| getRelatedDevices: function(direction) { |
| switch(direction) { |
| case DeviceSync.UP: |
| return union(this.travelSync.getRelatedDevices(DeviceSync.UP), |
| this.travelSync.getRelatedDevices(DeviceSync.FORWARDS)); |
| case DeviceSync.DOWN: |
| return union(this.travelSync.getRelatedDevices(DeviceSync.DOWN), |
| this.travelSync.getRelatedDevices(DeviceSync.BACKWARDS)); |
| default: |
| return this.travelSync.getRelatedDevices(direction); |
| } |
| }, |
| |
| interpretCastVector: function(v, spec) { |
| var self = this; |
| |
| var direction = getGestureDirection(v); |
| var related = this.getRelatedDevices(direction); |
| if (related.size === 1) { |
| related.forEach(function(deviceName, owner) { |
| self.cast(owner, deviceName, spec).catch(self.onError); |
| }); |
| } else { |
| var unknown = this.travelSync.getUnconnectedCastTargets(); |
| |
| if (related.size === 0 && unknown.size === 1) { |
| unknown.forEach(function(deviceName, owner) { |
| Promise.all([ |
| self.cast(owner, deviceName, spec), |
| self.travelSync.relateDevice(owner, deviceName, { |
| direction: direction, |
| magnitude: DeviceSync.NEAR |
| }) |
| ]).catch(self.onError); |
| }); |
| } else { |
| var all = this.travelSync.getPossibleCastTargets(); |
| var other = difference(all, related); |
| |
| if (related.size > 0 || unknown.size > 0 || other.size > 0) { |
| this.onAmbiguousCast(related, unknown, other); |
| } else { |
| this.onNoNearbyDevices(); |
| } |
| } |
| } |
| }, |
| |
| cast: function(targetOwner, targetDeviceName, spec) { |
| var self = this; |
| |
| return this.travelSync.cast(targetOwner, targetDeviceName, spec) |
| .then(function() { |
| self.onCast(spec); |
| }, this.onError); |
| } |
| }, |
| |
| events: { |
| onCast: '', |
| /** |
| * @param related owner => device multimap of related cast candidates |
| * @param unknown owner => device multimap of unconnected cast candidates |
| * @param other owner => device multimap of unrelated connected cast |
| * candidates |
| */ |
| onAmbiguousCast: '', |
| /** |
| * Triggered when a cast is attempted but there are no known nearby devices. |
| */ |
| onNoNearbyDevices: '', |
| onError: 'memory' |
| }, |
| |
| init: function(travelSync, domRoot) { |
| this.travelSync = travelSync; |
| } |
| }); |
| |
| module.exports = CastingManager; |