1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 sc_require('platform'); 9 10 /** 11 ## Device Motion 12 13 When a device is moved, some platforms will inform us of its movement 14 (accelerometer) and angle (gyroscope). 15 16 */ 17 SC.mixin(SC.device, 18 /** @scope SC.device */ { 19 20 // .......................................................... 21 // PROPERTIES 22 // 23 24 /* 25 TODO [CC] There shouldn't be a need for this property, we should overload 26 the addObserver/removeObserver functions and check for the 27 rotationX/Y/Z keys. 28 */ 29 /** 30 Because the device motion events fire every 50ms, for performance reasons 31 you must opt into listening for those events. 32 33 @type Boolean 34 @default NO 35 */ 36 listenForDeviceMotion: NO, 37 38 /** 39 Amount, in degrees, that the device is rotated around its x axis. x axis is 40 the plane of the screen and is positive towards the right hand side of 41 the screen, regardless of orientation. 42 43 SC.device#listenForDeviceMotion must be true for this property to update. 44 45 @type Number 46 @default 0 47 */ 48 rotationX: 0, 49 50 /** 51 Amount, in degrees, that the device is rotated around its y axis (axis runs 52 from the top to the bottom of the device). y axis is the plane of the 53 screen and is positive towards the top of the screen, regardless of 54 orientation. 55 56 SC.device#listenForDeviceMotion must be true for this property to update. 57 58 @type Number 59 @default 0 60 */ 61 rotationY: 0, 62 63 /** 64 Amount, in degrees, that the device is rotated around its z axis (normal 65 coming "out" of the screen). z axis is perpendicular to the screen and is 66 positive "out" of (or normal to) the screen. This property is only useful 67 if the device has a gyroscope; for devices which lack a gyroscope (use 68 an accelerometer or nothing), this property remains 0. 69 70 SC.device#listenForDeviceMotion must be true for this property to update. 71 72 @type Number 73 @default 0 74 */ 75 rotationZ: 0, 76 77 78 // .......................................................... 79 // SETUP 80 // 81 82 setupMotion: function() { 83 SC.RootResponder.responder.listenFor(['devicemotion', 'deviceorientation'], window, this); 84 }, 85 86 87 // .......................................................... 88 // DEVICE MOTION HANDLING 89 // 90 91 _scd_listenForDeviceMotionDidChange: function() { 92 if (!SC.RootResponder.responder) return; 93 94 // we only care about the gyro now, we don't need acceleration 95 if (this.get('listenForDeviceMotion')) { 96 if (SC.platform.hasGyroscope) { 97 SC.Event.add(window, 'deviceorientation', this, this._scd_deviceorientationPoll); 98 } else if (SC.platform.hasAccelerometer) { 99 SC.Event.add(window, 'devicemotion', this, this._scd_devicemotionPoll); 100 } else { 101 SC.Logger.warn("Can't listen for device motion events on a platform that does not support them"); 102 } 103 } else { 104 SC.Event.remove(window, 'deviceorientation', this, this._scd_deviceorientationPoll); 105 SC.Event.remove(window, 'devicemotion', this, this._scd_devicemotionPoll); 106 } 107 }, 108 109 /** 110 Fired once every 50ms, informing us of the gyroscope measurements 111 */ 112 deviceorientation: function(evt) { 113 evt = evt.originalEvent; 114 115 if (!SC.platform.hasGyroscope) SC.platform.hasGyroscope = YES; 116 117 SC.Event.remove(window, 'deviceorientation', this, this.deviceorientation); 118 SC.Event.remove(window, 'devicemotion', this, this.devicemotion); 119 120 this.addObserver('listenForDeviceMotion', this, this._scd_listenForDeviceMotionDidChange); 121 this.notifyPropertyChange('listenForDeviceMotion'); 122 }, 123 124 /** @private 125 Gets called after the first deviceorientation event, used to actually set 126 the rotation values 127 */ 128 _scd_deviceorientationPoll: function(evt) { 129 var orientation = this.get('orientation'); 130 131 evt = evt.originalEvent; 132 133 SC.run(function() { 134 this.beginPropertyChanges(); 135 this.set('rotationX', orientation !== 'landscape' ? evt.beta : evt.gamma); 136 this.set('rotationY', orientation !== 'landscape' ? evt.gamma : -evt.beta); 137 this.set('rotationZ', evt.alpha); 138 this.endPropertyChanges(); 139 }, this); 140 }, 141 142 /** @private 143 Because we only want to deal with only the gyroscope, if it is firing its event, 144 we wait until two devicemotion events have been fired to decide to use the 145 accelerometer instead. 146 */ 147 _devicemotionCalled: NO, 148 149 /** 150 Fired every 50ms, informing us of the accelerometer measurements 151 */ 152 devicemotion: function(evt) { 153 if (!SC.platform.hasAccelerometer) SC.platform.hasAccelerometer = YES; 154 155 if (this._devicemotionCalled) { 156 // if this happens, and this event fires (ie. the deviceorientation event hasn't) 157 // then we assume the device does not have a gyroscope 158 SC.Event.remove(window, 'deviceorientation', this, this.deviceorientation); 159 SC.Event.remove(window, 'devicemotion', this, this.devicemotion); 160 161 this.set('rotationZ', 0); // ensure rotationZ does not get any other value 162 this.addObserver('listenForDeviceMotion', this, this._scd_listenForDeviceMotionDidChange); 163 this.notifyPropertyChange('listenForDeviceMotion'); 164 } else { 165 this._devicemotionCalled = YES; 166 } 167 }, 168 169 /* 170 TODO [CC] This needs to be expanded on a bit. If you hold the device in weird 171 positions, the rotation values from the accelerometer no longer match those 172 from the gyroscope. What about people who use their iPad in bed?! 173 */ 174 /** @private 175 Gets called after the first devicemotion event, if the device has no gyropscope. 176 */ 177 _scd_devicemotionPoll: function(evt) { 178 var min = SC.platform.accelerationMinimum, 179 max = SC.platform.accelerationMaximum, 180 spread = max - min, 181 orientation = this.get('orientation'), 182 aX, aY, rX, rY, swap; 183 184 evt = evt.originalEvent; 185 186 aX = evt.accelerationIncludingGravity.x; 187 aY = evt.accelerationIncludingGravity.y; 188 189 if (aX < min) aX = min; 190 else if (aX > max) aX = max; 191 if (aY < min) aY = min; 192 else if (aY > max) aY = max; 193 194 if (orientation === 'landscape') { 195 swap = aX; 196 aX = aY; 197 aY = swap; 198 } 199 200 rX = 90 - ((aY-min)/spread) * 180; 201 rY = -90 + ((aX-min)/spread) * 180; 202 203 SC.run(function() { 204 this.beginPropertyChanges(); 205 this.set('rotationX', rX); 206 this.set('rotationY', rY); 207 this.endPropertyChanges(); 208 }, this); 209 } 210 211 }); 212 213 SC.ready(function() { 214 SC.device.setupMotion(); 215 }); 216