// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2010 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== sc_require('platform'); /** h1. Device Motion When a device is moved, some platforms will inform us of its movement (accelerometer) and angle (gyroscope). */ SC.mixin(SC.device, /** @scope SC.device */ { // .......................................................... // PROPERTIES // /* TODO [CC] There shouldn't be a need for this property, we should overload the addObserver/removeObserver functions and check for the rotationX/Y/Z keys. */ /** Because the device motion events fire every 50ms, for performance reasons you must opt into listening for those events. @property {Boolean} @default NO */ listenForDeviceMotion: NO, /** Amount, in degrees, that the device is rotated around its x axis. x axis is the plane of the screen and is positive towards the right hand side of the screen, regardless of orientation. SC.device#listenForDeviceMotion must be true for this property to update. @property {Number} @default 0 */ rotationX: 0, /** Amount, in degrees, that the device is rotated around its y axis (axis runs from the top to the bottom of the device). y axis is the plane of the screen and is positive towards the top of the screen, regardless of orientation. SC.device#listenForDeviceMotion must be true for this property to update. @property {Number} @default 0 */ rotationY: 0, /** Amount, in degrees, that the device is rotated around its z axis (normal coming "out" of the screen). z axis is perpendicular to the screen and is positive "out" of (or normal to) the screen. This property is only useful if the device has a gyroscope; for devices which lack a gyroscope (use an accelerometer or nothing), this property remains 0. SC.device#listenForDeviceMotion must be true for this property to update. @property {Number} @default 0 */ rotationZ: 0, // .......................................................... // SETUP // setupMotion: function() { SC.RootResponder.responder.listenFor('devicemotion deviceorientation'.w(), window, this); }, // .......................................................... // DEVICE MOTION HANDLING // _scd_listenForDeviceMotionDidChange: function() { if (!SC.RootResponder.responder) return; // we only care about the gyro now, we don't need acceleration if (this.get('listenForDeviceMotion')) { if (SC.platform.hasGyroscope) { SC.Event.add(window, 'deviceorientation', this, this._scd_deviceorientationPoll); } else if (SC.platform.hasAccelerometer) { SC.Event.add(window, 'devicemotion', this, this._scd_devicemotionPoll); } else { SC.Logger.warn("Can't listen for device motion events on a platform that does not support them"); } } else { SC.Event.remove(window, 'deviceorientation', this, this._scd_deviceorientationPoll); SC.Event.remove(window, 'devicemotion', this, this._scd_devicemotionPoll); } }, /** Fired once every 50ms, informing us of the gyroscope measurements */ deviceorientation: function(evt) { evt = evt.originalEvent; if (!SC.platform.hasGyroscope) SC.platform.hasGyroscope = YES; SC.Event.remove(window, 'deviceorientation', this, this.deviceorientation); SC.Event.remove(window, 'devicemotion', this, this.devicemotion); this.addObserver('listenForDeviceMotion', this, this._scd_listenForDeviceMotionDidChange); this.notifyPropertyChange('listenForDeviceMotion'); }, /** @private Gets called after the first deviceorientation event, used to actually set the rotation values */ _scd_deviceorientationPoll: function(evt) { var orientation = this.get('orientation'); evt = evt.originalEvent; SC.run(function() { this.beginPropertyChanges(); this.set('rotationX', orientation !== 'landscape' ? evt.beta : evt.gamma); this.set('rotationY', orientation !== 'landscape' ? evt.gamma : -evt.beta); this.set('rotationZ', evt.alpha); this.endPropertyChanges(); }, this); }, /** @private Because we only want to deal with only the gyroscope, if it is firing its event, we wait until two devicemotion events have been fired to decide to use the accelerometer instead. */ _devicemotionCalled: NO, /** Fired every 50ms, informing us of the accelerometer measurements */ devicemotion: function(evt) { if (!SC.platform.hasAccelerometer) SC.platform.hasAccelerometer = YES; if (this._devicemotionCalled) { // if this happens, and this event fires (ie. the deviceorientation event hasn't) // then we assume the device does not have a gyroscope SC.Event.remove(window, 'deviceorientation', this, this.deviceorientation); SC.Event.remove(window, 'devicemotion', this, this.devicemotion); this.set('rotationZ', 0); // ensure rotationZ does not get any other value this.addObserver('listenForDeviceMotion', this, this._scd_listenForDeviceMotionDidChange); this.notifyPropertyChange('listenForDeviceMotion'); } else { this._devicemotionCalled = YES; } }, /* TODO [CC] This needs to be expanded on a bit. If you hold the device in weird positions, the rotation values from the accelerometer no longer match those from the gyroscope. What about people who use their iPad in bed?! */ /** @private Gets called after the first devicemotion event, if the device has no gyropscope. */ _scd_devicemotionPoll: function(evt) { var min = SC.platform.accelerationMinimum, max = SC.platform.accelerationMaximum, spread = max - min, orientation = this.get('orientation'), aX, aY, rX, rY, swap; evt = evt.originalEvent; aX = evt.accelerationIncludingGravity.x; aY = evt.accelerationIncludingGravity.y; if (aX < min) aX = min; else if (aX > max) aX = max; if (aY < min) aY = min; else if (aY > max) aY = max; if (orientation === 'landscape') { swap = aX; aX = aY; aY = swap; } rX = 90 - ((aY-min)/spread) * 180; rY = -90 + ((aX-min)/spread) * 180; SC.run(function() { this.beginPropertyChanges(); this.set('rotationX', rX); this.set('rotationY', rY); this.endPropertyChanges(); }, this); } }); SC.ready(function() { SC.device.setupMotion(); });