├── .meteor ├── .gitignore ├── release ├── platforms ├── .id ├── .finished-upgraders ├── packages └── versions ├── .gitignore ├── README.md ├── client ├── App.jsx ├── meteor-react-cornerstone-example.jsx ├── meteor-react-cornerstone-example.html ├── components │ ├── Image.styl │ └── Image.jsx └── compatibility │ ├── cornerstoneMath.js │ └── hammer.js └── LICENSE /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.0.2 2 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .meteor/local 2 | .meteor/meteorite 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meteor-react-cornerstone-example 2 | Example of using cornerstone from a Meteor application with React 3 | 4 | Try this out live [here](http://meteor-react-cornerstone-example.meteor.com/) -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | // App component - represents the whole app 2 | App = React.createClass({ 3 | renderImage() { 4 | return ; 5 | }, 6 | 7 | render() { 8 | return ( 9 |
10 | {this.renderImage()} 11 |
12 | ); 13 | } 14 | }); -------------------------------------------------------------------------------- /client/meteor-react-cornerstone-example.jsx: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | // This code is executed on the client only 3 | 4 | Meteor.startup(function () { 5 | // Use Meteor.startup to render the component after the page is ready 6 | React.render(, document.getElementById("render-target")); 7 | }); 8 | } -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1g7o422zf6d7ykoiz6p 8 | -------------------------------------------------------------------------------- /client/meteor-react-cornerstone-example.html: -------------------------------------------------------------------------------- 1 | 2 | meteor-react-cornerstone-example 3 | 4 | 5 | 6 | 10 |
11 |
12 | 13 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | insecure 8 | twbs:bootstrap 9 | react 10 | jquery 11 | standard-minifiers 12 | meteor-base 13 | mobile-experience 14 | mongo 15 | blaze-html-templates 16 | session 17 | tracker 18 | logging 19 | reload 20 | random 21 | ejson 22 | spacebars 23 | check 24 | stylus 25 | -------------------------------------------------------------------------------- /client/components/Image.styl: -------------------------------------------------------------------------------- 1 | .viewportContainer 2 | position: relative 3 | height: 100% 4 | width: 100% 5 | 6 | .viewportElement 7 | top: 0px 8 | left: 0px 9 | position:absolute 10 | height: 100% 11 | width: 100% 12 | 13 | .dicomTag 14 | position: absolute 15 | color: #A9A9A9 16 | 17 | .topLeft 18 | top: 3px 19 | left: 5px 20 | 21 | .topRight 22 | top: 3px 23 | right: 5px 24 | 25 | .bottomRight 26 | bottom: 5px 27 | right: 5px 28 | 29 | .bottomLeft 30 | bottom: 5px 31 | left: 5px -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 OHIF 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | autoupdate@1.2.3 2 | babel-compiler@5.8.24_1 3 | babel-runtime@0.1.4 4 | base64@1.0.4 5 | binary-heap@1.0.4 6 | blaze@2.1.3 7 | blaze-html-templates@1.0.1 8 | blaze-tools@1.0.4 9 | boilerplate-generator@1.0.4 10 | caching-compiler@1.0.0 11 | caching-html-compiler@1.0.2 12 | callback-hook@1.0.4 13 | check@1.0.6 14 | coffeescript@1.0.10 15 | cosmos:browserify@0.7.1 16 | ddp@1.2.2 17 | ddp-client@1.2.1 18 | ddp-common@1.2.1 19 | ddp-server@1.2.1 20 | deps@1.0.9 21 | diff-sequence@1.0.1 22 | ecmascript@0.1.5 23 | ecmascript-collections@0.1.6 24 | ejson@1.0.7 25 | fastclick@1.0.7 26 | geojson-utils@1.0.4 27 | hot-code-push@1.0.0 28 | html-tools@1.0.5 29 | htmljs@1.0.5 30 | http@1.1.1 31 | id-map@1.0.4 32 | insecure@1.0.4 33 | jquery@1.11.4 34 | jsx@0.2.1 35 | launch-screen@1.0.4 36 | livedata@1.0.15 37 | logging@1.0.8 38 | meteor@1.1.9 39 | meteor-base@1.0.1 40 | minifiers@1.1.7 41 | minimongo@1.0.10 42 | mobile-experience@1.0.1 43 | mobile-status-bar@1.0.6 44 | mongo@1.1.2 45 | mongo-id@1.0.1 46 | npm-mongo@1.4.39_1 47 | observe-sequence@1.0.7 48 | ordered-dict@1.0.4 49 | promise@0.5.0 50 | random@1.0.4 51 | react@0.1.13 52 | react-meteor-data@0.1.9 53 | react-runtime@0.13.3_7 54 | react-runtime-dev@0.13.3_7 55 | react-runtime-prod@0.13.3_6 56 | reactive-dict@1.1.2 57 | reactive-var@1.0.6 58 | reload@1.1.4 59 | retry@1.0.4 60 | routepolicy@1.0.6 61 | session@1.1.1 62 | spacebars@1.0.7 63 | spacebars-compiler@1.0.7 64 | standard-minifiers@1.0.1 65 | stylus@2.511.0_2 66 | templating@1.1.4 67 | templating-tools@1.0.0 68 | tracker@1.0.9 69 | twbs:bootstrap@3.3.5 70 | ui@1.0.8 71 | underscore@1.0.4 72 | url@1.0.5 73 | webapp@1.2.2 74 | webapp-hashing@1.0.5 75 | -------------------------------------------------------------------------------- /client/components/Image.jsx: -------------------------------------------------------------------------------- 1 | Image = React.createClass({ 2 | getInitialState: function() { 3 | return { 4 | wwwc: '', 5 | zoom: 1.0 6 | }; 7 | }, 8 | 9 | onImageRendered() { 10 | var domNode = $(this.getDOMNode()); 11 | var topLeft = domNode.find(".topLeft"); 12 | var topRight = domNode.find(".topRight"); 13 | var bottomRight = domNode.find(".bottomRight"); 14 | var bottomLeft = domNode.find(".bottomLeft"); 15 | 16 | var element = domNode.find(".viewportElement").get(0); 17 | var viewport = cornerstone.getViewport(element) 18 | 19 | this.setState({ 20 | wwwc: Math.round(viewport.voi.windowWidth) + "/" + Math.round(viewport.voi.windowCenter), 21 | zoom: viewport.scale.toFixed(2) 22 | }); 23 | }, 24 | 25 | returnFalse(e) { 26 | e.stopPropagation(); 27 | e.preventDefault(); 28 | }, 29 | 30 | handleResize() { 31 | this.updateHeight(); 32 | var domNode = this.getDOMNode(); 33 | var element = $(domNode).find('.viewportElement').get(0); 34 | cornerstone.resize(element, true); 35 | }, 36 | 37 | updateHeight() { 38 | var domNode = this.getDOMNode(); 39 | var container = $(domNode); 40 | // Subtract the header height and some padding 41 | var windowHeight = $(window).height() - $("#header").height() - 10 ; 42 | container.css({ 43 | height: windowHeight 44 | }); 45 | }, 46 | 47 | componentDidMount() { 48 | this.updateHeight(); 49 | var domNode = this.getDOMNode(); 50 | var element = $(domNode).find('.viewportElement').get(0); 51 | $(element).on("CornerstoneImageRendered", this.onImageRendered); 52 | window.addEventListener('resize', this.handleResize); 53 | 54 | 55 | 56 | cornerstone.enable(element); 57 | var imageId = "example://1"; 58 | cornerstone.loadImage(imageId).then(function(image) { 59 | cornerstone.displayImage(element, image); 60 | cornerstoneTools.mouseInput.enable(element); 61 | cornerstoneTools.mouseWheelInput.enable(element); 62 | cornerstoneTools.wwwc.activate(element, 1); // ww/wc is the default tool for left mouse button 63 | cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button 64 | cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button 65 | cornerstoneTools.zoomWheel.activate(element); // zoom is the default tool for middle mouse wheel 66 | 67 | cornerstoneTools.touchInput.enable(element); 68 | cornerstoneTools.panTouchDrag.activate(element); 69 | cornerstoneTools.zoomTouchPinch.activate(element); 70 | }); 71 | }, 72 | 73 | componentWillUnmount() { 74 | var element = $(domNode).find('.viewportElement').get(0); 75 | $(element).off("CornerstoneImageRendered", this.onImageRendered); 76 | window.removeEventListener('resize', this.handleResize); 77 | }, 78 | 79 | render() { 80 | return ( 81 |
86 |
87 |
88 |
89 | Patient Name 90 |
91 |
92 | Hospital 93 |
94 |
95 | Zoom: {this.state.zoom} 96 |
97 |
98 | WW/WC: {this.state.wwwc} 99 |
100 |
101 | ); 102 | } 103 | }); -------------------------------------------------------------------------------- /client/compatibility/cornerstoneMath.js: -------------------------------------------------------------------------------- 1 | /*! cornerstoneMath - v0.1.2 - 2015-08-31 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneMath */ 2 | // Begin Source: src/vector3.js 3 | // Based on THREE.JS 4 | 5 | var cornerstoneMath = (function (cornerstoneMath) { 6 | 7 | "use strict"; 8 | 9 | if(cornerstoneMath === undefined) { 10 | cornerstoneMath = {}; 11 | } 12 | 13 | cornerstoneMath.Vector3 = function ( x, y, z ) { 14 | 15 | this.x = x || 0; 16 | this.y = y || 0; 17 | this.z = z || 0; 18 | 19 | }; 20 | 21 | cornerstoneMath.Vector3.prototype = { 22 | 23 | constructor: cornerstoneMath.Vector3, 24 | 25 | set: function ( x, y, z ) { 26 | 27 | this.x = x; 28 | this.y = y; 29 | this.z = z; 30 | 31 | return this; 32 | 33 | }, 34 | 35 | setX: function ( x ) { 36 | 37 | this.x = x; 38 | 39 | return this; 40 | 41 | }, 42 | 43 | setY: function ( y ) { 44 | 45 | this.y = y; 46 | 47 | return this; 48 | 49 | }, 50 | 51 | setZ: function ( z ) { 52 | 53 | this.z = z; 54 | 55 | return this; 56 | 57 | }, 58 | 59 | setComponent: function ( index, value ) { 60 | 61 | switch ( index ) { 62 | 63 | case 0: this.x = value; break; 64 | case 1: this.y = value; break; 65 | case 2: this.z = value; break; 66 | default: throw new Error( "index is out of range: " + index ); 67 | 68 | } 69 | 70 | }, 71 | 72 | getComponent: function ( index ) { 73 | 74 | switch ( index ) { 75 | 76 | case 0: return this.x; 77 | case 1: return this.y; 78 | case 2: return this.z; 79 | default: throw new Error( "index is out of range: " + index ); 80 | 81 | } 82 | 83 | }, 84 | 85 | copy: function ( v ) { 86 | 87 | this.x = v.x; 88 | this.y = v.y; 89 | this.z = v.z; 90 | 91 | return this; 92 | 93 | }, 94 | 95 | add: function ( v, w ) { 96 | 97 | if ( w !== undefined ) { 98 | 99 | console.warn( 'DEPRECATED: Vector3\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); 100 | return this.addVectors( v, w ); 101 | 102 | } 103 | 104 | this.x += v.x; 105 | this.y += v.y; 106 | this.z += v.z; 107 | 108 | return this; 109 | 110 | }, 111 | 112 | addScalar: function ( s ) { 113 | 114 | this.x += s; 115 | this.y += s; 116 | this.z += s; 117 | 118 | return this; 119 | 120 | }, 121 | 122 | addVectors: function ( a, b ) { 123 | 124 | this.x = a.x + b.x; 125 | this.y = a.y + b.y; 126 | this.z = a.z + b.z; 127 | 128 | return this; 129 | 130 | }, 131 | 132 | sub: function ( v, w ) { 133 | 134 | if ( w !== undefined ) { 135 | 136 | console.warn( 'DEPRECATED: Vector3\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); 137 | return this.subVectors( v, w ); 138 | 139 | } 140 | 141 | this.x -= v.x; 142 | this.y -= v.y; 143 | this.z -= v.z; 144 | 145 | return this; 146 | 147 | }, 148 | 149 | subVectors: function ( a, b ) { 150 | 151 | this.x = a.x - b.x; 152 | this.y = a.y - b.y; 153 | this.z = a.z - b.z; 154 | 155 | return this; 156 | 157 | }, 158 | 159 | multiply: function ( v, w ) { 160 | 161 | if ( w !== undefined ) { 162 | 163 | console.warn( 'DEPRECATED: Vector3\'s .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); 164 | return this.multiplyVectors( v, w ); 165 | 166 | } 167 | 168 | this.x *= v.x; 169 | this.y *= v.y; 170 | this.z *= v.z; 171 | 172 | return this; 173 | 174 | }, 175 | 176 | multiplyScalar: function ( scalar ) { 177 | 178 | this.x *= scalar; 179 | this.y *= scalar; 180 | this.z *= scalar; 181 | 182 | return this; 183 | 184 | }, 185 | 186 | multiplyVectors: function ( a, b ) { 187 | 188 | this.x = a.x * b.x; 189 | this.y = a.y * b.y; 190 | this.z = a.z * b.z; 191 | 192 | return this; 193 | 194 | }, 195 | 196 | 197 | applyAxisAngle: function () { 198 | 199 | var quaternion; 200 | 201 | return function ( axis, angle ) { 202 | 203 | if ( quaternion === undefined ) quaternion = new cornerstoneMath.Quaternion(); 204 | 205 | this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); 206 | 207 | return this; 208 | 209 | }; 210 | 211 | }(), 212 | 213 | applyMatrix3: function ( m ) { 214 | 215 | var x = this.x; 216 | var y = this.y; 217 | var z = this.z; 218 | 219 | var e = m.elements; 220 | 221 | this.x = e[0] * x + e[3] * y + e[6] * z; 222 | this.y = e[1] * x + e[4] * y + e[7] * z; 223 | this.z = e[2] * x + e[5] * y + e[8] * z; 224 | 225 | return this; 226 | 227 | }, 228 | 229 | applyMatrix4: function ( m ) { 230 | 231 | // input: THREE.Matrix4 affine matrix 232 | 233 | var x = this.x, y = this.y, z = this.z; 234 | 235 | var e = m.elements; 236 | 237 | this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; 238 | this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; 239 | this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; 240 | 241 | return this; 242 | 243 | }, 244 | 245 | applyProjection: function ( m ) { 246 | 247 | // input: THREE.Matrix4 projection matrix 248 | 249 | var x = this.x, y = this.y, z = this.z; 250 | 251 | var e = m.elements; 252 | var d = 1 / ( e[3] * x + e[7] * y + e[11] * z + e[15] ); // perspective divide 253 | 254 | this.x = ( e[0] * x + e[4] * y + e[8] * z + e[12] ) * d; 255 | this.y = ( e[1] * x + e[5] * y + e[9] * z + e[13] ) * d; 256 | this.z = ( e[2] * x + e[6] * y + e[10] * z + e[14] ) * d; 257 | 258 | return this; 259 | 260 | }, 261 | 262 | applyQuaternion: function ( q ) { 263 | 264 | var x = this.x; 265 | var y = this.y; 266 | var z = this.z; 267 | 268 | var qx = q.x; 269 | var qy = q.y; 270 | var qz = q.z; 271 | var qw = q.w; 272 | 273 | // calculate quat * vector 274 | 275 | var ix = qw * x + qy * z - qz * y; 276 | var iy = qw * y + qz * x - qx * z; 277 | var iz = qw * z + qx * y - qy * x; 278 | var iw = -qx * x - qy * y - qz * z; 279 | 280 | // calculate result * inverse quat 281 | 282 | this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; 283 | this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; 284 | this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; 285 | 286 | return this; 287 | 288 | }, 289 | 290 | transformDirection: function ( m ) { 291 | 292 | // input: THREE.Matrix4 affine matrix 293 | // vector interpreted as a direction 294 | 295 | var x = this.x, y = this.y, z = this.z; 296 | 297 | var e = m.elements; 298 | 299 | this.x = e[0] * x + e[4] * y + e[8] * z; 300 | this.y = e[1] * x + e[5] * y + e[9] * z; 301 | this.z = e[2] * x + e[6] * y + e[10] * z; 302 | 303 | this.normalize(); 304 | 305 | return this; 306 | 307 | }, 308 | 309 | divide: function ( v ) { 310 | 311 | this.x /= v.x; 312 | this.y /= v.y; 313 | this.z /= v.z; 314 | 315 | return this; 316 | 317 | }, 318 | 319 | divideScalar: function ( scalar ) { 320 | 321 | if ( scalar !== 0 ) { 322 | 323 | var invScalar = 1 / scalar; 324 | 325 | this.x *= invScalar; 326 | this.y *= invScalar; 327 | this.z *= invScalar; 328 | 329 | } else { 330 | 331 | this.x = 0; 332 | this.y = 0; 333 | this.z = 0; 334 | 335 | } 336 | 337 | return this; 338 | 339 | }, 340 | 341 | min: function ( v ) { 342 | 343 | if ( this.x > v.x ) { 344 | 345 | this.x = v.x; 346 | 347 | } 348 | 349 | if ( this.y > v.y ) { 350 | 351 | this.y = v.y; 352 | 353 | } 354 | 355 | if ( this.z > v.z ) { 356 | 357 | this.z = v.z; 358 | 359 | } 360 | 361 | return this; 362 | 363 | }, 364 | 365 | max: function ( v ) { 366 | 367 | if ( this.x < v.x ) { 368 | 369 | this.x = v.x; 370 | 371 | } 372 | 373 | if ( this.y < v.y ) { 374 | 375 | this.y = v.y; 376 | 377 | } 378 | 379 | if ( this.z < v.z ) { 380 | 381 | this.z = v.z; 382 | 383 | } 384 | 385 | return this; 386 | 387 | }, 388 | 389 | clamp: function ( min, max ) { 390 | 391 | // This function assumes min < max, if this assumption isn't true it will not operate correctly 392 | 393 | if ( this.x < min.x ) { 394 | 395 | this.x = min.x; 396 | 397 | } else if ( this.x > max.x ) { 398 | 399 | this.x = max.x; 400 | 401 | } 402 | 403 | if ( this.y < min.y ) { 404 | 405 | this.y = min.y; 406 | 407 | } else if ( this.y > max.y ) { 408 | 409 | this.y = max.y; 410 | 411 | } 412 | 413 | if ( this.z < min.z ) { 414 | 415 | this.z = min.z; 416 | 417 | } else if ( this.z > max.z ) { 418 | 419 | this.z = max.z; 420 | 421 | } 422 | 423 | return this; 424 | 425 | }, 426 | 427 | clampScalar: ( function () { 428 | 429 | var min, max; 430 | 431 | return function ( minVal, maxVal ) { 432 | 433 | if ( min === undefined ) { 434 | 435 | min = new cornerstoneMath.Vector3(); 436 | max = new cornerstoneMath.Vector3(); 437 | 438 | } 439 | 440 | min.set( minVal, minVal, minVal ); 441 | max.set( maxVal, maxVal, maxVal ); 442 | 443 | return this.clamp( min, max ); 444 | 445 | }; 446 | 447 | } )(), 448 | 449 | floor: function () { 450 | 451 | this.x = Math.floor( this.x ); 452 | this.y = Math.floor( this.y ); 453 | this.z = Math.floor( this.z ); 454 | 455 | return this; 456 | 457 | }, 458 | 459 | ceil: function () { 460 | 461 | this.x = Math.ceil( this.x ); 462 | this.y = Math.ceil( this.y ); 463 | this.z = Math.ceil( this.z ); 464 | 465 | return this; 466 | 467 | }, 468 | 469 | round: function () { 470 | 471 | this.x = Math.round( this.x ); 472 | this.y = Math.round( this.y ); 473 | this.z = Math.round( this.z ); 474 | 475 | return this; 476 | 477 | }, 478 | 479 | roundToZero: function () { 480 | 481 | this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); 482 | this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); 483 | this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); 484 | 485 | return this; 486 | 487 | }, 488 | 489 | negate: function () { 490 | 491 | return this.multiplyScalar( - 1 ); 492 | 493 | }, 494 | 495 | dot: function ( v ) { 496 | 497 | return this.x * v.x + this.y * v.y + this.z * v.z; 498 | 499 | }, 500 | 501 | lengthSq: function () { 502 | 503 | return this.x * this.x + this.y * this.y + this.z * this.z; 504 | 505 | }, 506 | 507 | length: function () { 508 | 509 | return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); 510 | 511 | }, 512 | 513 | lengthManhattan: function () { 514 | 515 | return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); 516 | 517 | }, 518 | 519 | normalize: function () { 520 | 521 | return this.divideScalar( this.length() ); 522 | 523 | }, 524 | 525 | setLength: function ( l ) { 526 | 527 | var oldLength = this.length(); 528 | 529 | if ( oldLength !== 0 && l !== oldLength ) { 530 | 531 | this.multiplyScalar( l / oldLength ); 532 | } 533 | 534 | return this; 535 | 536 | }, 537 | 538 | lerp: function ( v, alpha ) { 539 | 540 | this.x += ( v.x - this.x ) * alpha; 541 | this.y += ( v.y - this.y ) * alpha; 542 | this.z += ( v.z - this.z ) * alpha; 543 | 544 | return this; 545 | 546 | }, 547 | 548 | cross: function ( v, w ) { 549 | 550 | if ( w !== undefined ) { 551 | 552 | console.warn( 'DEPRECATED: Vector3\'s .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); 553 | return this.crossVectors( v, w ); 554 | 555 | } 556 | 557 | var x = this.x, y = this.y, z = this.z; 558 | 559 | this.x = y * v.z - z * v.y; 560 | this.y = z * v.x - x * v.z; 561 | this.z = x * v.y - y * v.x; 562 | 563 | return this; 564 | 565 | }, 566 | 567 | crossVectors: function ( a, b ) { 568 | 569 | var ax = a.x, ay = a.y, az = a.z; 570 | var bx = b.x, by = b.y, bz = b.z; 571 | 572 | this.x = ay * bz - az * by; 573 | this.y = az * bx - ax * bz; 574 | this.z = ax * by - ay * bx; 575 | 576 | return this; 577 | 578 | }, 579 | 580 | projectOnVector: function () { 581 | 582 | var v1, dot; 583 | 584 | return function ( vector ) { 585 | 586 | if ( v1 === undefined ) v1 = new cornerstoneMath.Vector3(); 587 | 588 | v1.copy( vector ).normalize(); 589 | 590 | dot = this.dot( v1 ); 591 | 592 | return this.copy( v1 ).multiplyScalar( dot ); 593 | 594 | }; 595 | 596 | }(), 597 | 598 | projectOnPlane: function () { 599 | 600 | var v1; 601 | 602 | return function ( planeNormal ) { 603 | 604 | if ( v1 === undefined ) v1 = new cornerstoneMath.Vector3(); 605 | 606 | v1.copy( this ).projectOnVector( planeNormal ); 607 | 608 | return this.sub( v1 ); 609 | 610 | }; 611 | 612 | }(), 613 | 614 | reflect: function () { 615 | 616 | // reflect incident vector off plane orthogonal to normal 617 | // normal is assumed to have unit length 618 | 619 | var v1; 620 | 621 | return function ( normal ) { 622 | 623 | if ( v1 === undefined ) v1 = new cornerstoneMath.Vector3(); 624 | 625 | return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); 626 | 627 | }; 628 | 629 | }(), 630 | 631 | angleTo: function ( v ) { 632 | 633 | var theta = this.dot( v ) / ( this.length() * v.length() ); 634 | 635 | // clamp, to handle numerical problems 636 | 637 | return Math.acos( cornerstoneMath.clamp( theta, -1, 1 ) ); 638 | 639 | }, 640 | 641 | distanceTo: function ( v ) { 642 | 643 | return Math.sqrt( this.distanceToSquared( v ) ); 644 | 645 | }, 646 | 647 | distanceToSquared: function ( v ) { 648 | 649 | var dx = this.x - v.x; 650 | var dy = this.y - v.y; 651 | var dz = this.z - v.z; 652 | 653 | return dx * dx + dy * dy + dz * dz; 654 | 655 | }, 656 | 657 | setEulerFromRotationMatrix: function ( m, order ) { 658 | 659 | console.error( "REMOVED: Vector3\'s setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code."); 660 | 661 | }, 662 | 663 | setEulerFromQuaternion: function ( q, order ) { 664 | 665 | console.error( "REMOVED: Vector3\'s setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code."); 666 | 667 | }, 668 | 669 | getPositionFromMatrix: function ( m ) { 670 | 671 | console.warn( "DEPRECATED: Vector3\'s .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code." ); 672 | 673 | return this.setFromMatrixPosition( m ); 674 | 675 | }, 676 | 677 | getScaleFromMatrix: function ( m ) { 678 | 679 | console.warn( "DEPRECATED: Vector3\'s .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code." ); 680 | 681 | return this.setFromMatrixScale( m ); 682 | }, 683 | 684 | getColumnFromMatrix: function ( index, matrix ) { 685 | 686 | console.warn( "DEPRECATED: Vector3\'s .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code." ); 687 | 688 | return this.setFromMatrixColumn( index, matrix ); 689 | 690 | }, 691 | 692 | setFromMatrixPosition: function ( m ) { 693 | 694 | this.x = m.elements[ 12 ]; 695 | this.y = m.elements[ 13 ]; 696 | this.z = m.elements[ 14 ]; 697 | 698 | return this; 699 | 700 | }, 701 | 702 | setFromMatrixScale: function ( m ) { 703 | 704 | var sx = this.set( m.elements[ 0 ], m.elements[ 1 ], m.elements[ 2 ] ).length(); 705 | var sy = this.set( m.elements[ 4 ], m.elements[ 5 ], m.elements[ 6 ] ).length(); 706 | var sz = this.set( m.elements[ 8 ], m.elements[ 9 ], m.elements[ 10 ] ).length(); 707 | 708 | this.x = sx; 709 | this.y = sy; 710 | this.z = sz; 711 | 712 | return this; 713 | }, 714 | 715 | setFromMatrixColumn: function ( index, matrix ) { 716 | 717 | var offset = index * 4; 718 | 719 | var me = matrix.elements; 720 | 721 | this.x = me[ offset ]; 722 | this.y = me[ offset + 1 ]; 723 | this.z = me[ offset + 2 ]; 724 | 725 | return this; 726 | 727 | }, 728 | 729 | equals: function ( v ) { 730 | 731 | return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); 732 | 733 | }, 734 | 735 | fromArray: function ( array ) { 736 | 737 | this.x = array[ 0 ]; 738 | this.y = array[ 1 ]; 739 | this.z = array[ 2 ]; 740 | 741 | return this; 742 | 743 | }, 744 | 745 | toArray: function () { 746 | 747 | return [ this.x, this.y, this.z ]; 748 | 749 | }, 750 | 751 | clone: function () { 752 | 753 | return new cornerstoneMath.Vector3( this.x, this.y, this.z ); 754 | 755 | } 756 | 757 | }; 758 | 759 | return cornerstoneMath; 760 | }(cornerstoneMath)); 761 | // End Source; src/vector3.js 762 | 763 | // Begin Source: src/Line3.js 764 | // Copied from THREE.JS 765 | /** 766 | * @author bhouston / http://exocortex.com 767 | */ 768 | 769 | 770 | var cornerstoneMath = (function (cornerstoneMath) { 771 | 772 | "use strict"; 773 | 774 | if (cornerstoneMath === undefined) { 775 | cornerstoneMath = {}; 776 | } 777 | 778 | cornerstoneMath.Line3 = function ( start, end ) { 779 | 780 | this.start = ( start !== undefined ) ? start : new cornerstoneMath.Vector3(); 781 | this.end = ( end !== undefined ) ? end : new cornerstoneMath.Vector3(); 782 | 783 | }; 784 | 785 | cornerstoneMath.Line3.prototype = { 786 | 787 | constructor: cornerstoneMath.Line3, 788 | 789 | set: function ( start, end ) { 790 | 791 | this.start.copy( start ); 792 | this.end.copy( end ); 793 | 794 | return this; 795 | 796 | }, 797 | 798 | copy: function ( line ) { 799 | 800 | this.start.copy( line.start ); 801 | this.end.copy( line.end ); 802 | 803 | return this; 804 | 805 | }, 806 | 807 | center: function ( optionalTarget ) { 808 | 809 | var result = optionalTarget || new cornerstoneMath.Vector3(); 810 | return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); 811 | 812 | }, 813 | 814 | delta: function ( optionalTarget ) { 815 | 816 | var result = optionalTarget || new cornerstoneMath.Vector3(); 817 | return result.subVectors( this.end, this.start ); 818 | 819 | }, 820 | 821 | distanceSq: function () { 822 | 823 | return this.start.distanceToSquared( this.end ); 824 | 825 | }, 826 | 827 | distance: function () { 828 | 829 | return this.start.distanceTo( this.end ); 830 | 831 | }, 832 | 833 | at: function ( t, optionalTarget ) { 834 | 835 | var result = optionalTarget || new cornerstoneMath.Vector3(); 836 | 837 | return this.delta( result ).multiplyScalar( t ).add( this.start ); 838 | 839 | }, 840 | 841 | closestPointToPointParameter: function () { 842 | 843 | var startP = new cornerstoneMath.Vector3(); 844 | var startEnd = new cornerstoneMath.Vector3(); 845 | 846 | return function ( point, clampToLine ) { 847 | 848 | startP.subVectors( point, this.start ); 849 | startEnd.subVectors( this.end, this.start ); 850 | 851 | var startEnd2 = startEnd.dot( startEnd ); 852 | var startEnd_startP = startEnd.dot( startP ); 853 | 854 | var t = startEnd_startP / startEnd2; 855 | 856 | if ( clampToLine ) { 857 | 858 | t = cornerstoneMath.Math.clamp( t, 0, 1 ); 859 | 860 | } 861 | 862 | return t; 863 | 864 | }; 865 | 866 | }(), 867 | 868 | closestPointToPoint: function ( point, clampToLine, optionalTarget ) { 869 | 870 | var t = this.closestPointToPointParameter( point, clampToLine ); 871 | 872 | var result = optionalTarget || new cornerstoneMath.Vector3(); 873 | 874 | return this.delta( result ).multiplyScalar( t ).add( this.start ); 875 | 876 | }, 877 | 878 | applyMatrix4: function ( matrix ) { 879 | 880 | this.start.applyMatrix4( matrix ); 881 | this.end.applyMatrix4( matrix ); 882 | 883 | return this; 884 | 885 | }, 886 | 887 | equals: function ( line ) { 888 | 889 | return line.start.equals( this.start ) && line.end.equals( this.end ); 890 | 891 | }, 892 | 893 | clone: function () { 894 | 895 | return new cornerstoneMath.Line3().copy( this ); 896 | 897 | }, 898 | 899 | intersectLine: function ( line ) { 900 | // http://stackoverflow.com/questions/2316490/the-algorithm-to-find-the-point-of-intersection-of-two-3d-line-segment/10288710#10288710 901 | var da = this.end.clone().sub(this.start); 902 | var db = line.end.clone().sub(line.start); 903 | var dc = line.start.clone().sub(this.start); 904 | 905 | var daCrossDb = da.clone().cross(db); 906 | var dcCrossDb = dc.clone().cross(db); 907 | 908 | if (dc.dot(da) === 0){ 909 | // Lines are not coplanar, stop here 910 | return; 911 | } 912 | 913 | var s = dcCrossDb.dot(daCrossDb) / daCrossDb.lengthSq(); 914 | 915 | // Make sure we have an intersection 916 | if (s > 1.0 || isNaN(s)) { 917 | return; 918 | } 919 | 920 | var intersection = this.start.clone().add(da.clone().multiplyScalar(s)); 921 | var distanceTest = intersection.clone().sub(line.start).lengthSq() + intersection.clone().sub(line.end).lengthSq(); 922 | if (distanceTest <= line.distanceSq()) { 923 | return intersection; 924 | } 925 | return; 926 | } 927 | }; 928 | 929 | return cornerstoneMath; 930 | }(cornerstoneMath)); 931 | // End Source; src/Line3.js 932 | 933 | // Begin Source: src/lineSegment.js 934 | var cornerstoneMath = (function (cornerstoneMath) { 935 | 936 | "use strict"; 937 | 938 | if(cornerstoneMath === undefined) { 939 | cornerstoneMath = {}; 940 | } 941 | 942 | // based on http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment 943 | function sqr(x) 944 | { 945 | return x * x; 946 | } 947 | 948 | function dist2(v, w) { 949 | return sqr(v.x - w.x) + sqr(v.y - w.y); 950 | } 951 | 952 | function distanceToPointSquared(lineSegment, point) 953 | { 954 | var l2 = dist2(lineSegment.start, lineSegment.end); 955 | if(l2 === 0) { 956 | return dist2(point, lineSegment.start); 957 | } 958 | var t = ((point.x - lineSegment.start.x) * (lineSegment.end.x - lineSegment.start.x) + 959 | (point.y - lineSegment.start.y) * (lineSegment.end.y - lineSegment.start.y)) / l2; 960 | if(t < 0) { 961 | return dist2(point, lineSegment.start); 962 | } 963 | if(t > 1) { 964 | return dist2(point, lineSegment.end); 965 | } 966 | 967 | var pt = { 968 | x : lineSegment.start.x + t * (lineSegment.end.x - lineSegment.start.x), 969 | y : lineSegment.start.y + t * (lineSegment.end.y - lineSegment.start.y) 970 | }; 971 | return dist2(point, pt); 972 | } 973 | 974 | function distanceToPoint(lineSegment, point) 975 | { 976 | return Math.sqrt(distanceToPointSquared(lineSegment, point)); 977 | } 978 | 979 | // module exports 980 | cornerstoneMath.lineSegment = 981 | { 982 | distanceToPoint : distanceToPoint 983 | }; 984 | 985 | 986 | return cornerstoneMath; 987 | }(cornerstoneMath)); 988 | // End Source; src/lineSegment.js 989 | 990 | // Begin Source: src/math.js 991 | // Based on THREE.JS 992 | 993 | var cornerstoneMath = (function (cornerstoneMath) { 994 | 995 | "use strict"; 996 | 997 | if (cornerstoneMath === undefined) { 998 | cornerstoneMath = {}; 999 | } 1000 | 1001 | function clamp(x,a,b) { 1002 | return ( x < a ) ? a : ( ( x > b ) ? b : x ); 1003 | } 1004 | 1005 | function degToRad(degrees) { 1006 | var degreeToRadiansFactor = Math.PI / 180; 1007 | return degrees * degreeToRadiansFactor; 1008 | } 1009 | 1010 | function radToDeg(radians) { 1011 | var radianToDegreesFactor = 180 / Math.PI; 1012 | return radians * radianToDegreesFactor; 1013 | } 1014 | 1015 | cornerstoneMath.clamp = clamp; 1016 | cornerstoneMath.degToRad = degToRad; 1017 | cornerstoneMath.radToDeg = radToDeg; 1018 | 1019 | return cornerstoneMath; 1020 | }(cornerstoneMath)); 1021 | // End Source; src/math.js 1022 | 1023 | // Begin Source: src/matrix4.js 1024 | // Based on THREE.JS 1025 | 1026 | var cornerstoneMath = (function (cornerstoneMath) { 1027 | 1028 | "use strict"; 1029 | 1030 | if(cornerstoneMath === undefined) { 1031 | cornerstoneMath = {}; 1032 | } 1033 | 1034 | cornerstoneMath.Matrix4 = function Matrix4(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) 1035 | { 1036 | this.elements = new Float32Array( 16 ); 1037 | 1038 | // TODO: if n11 is undefined, then just set to identity, otherwise copy all other values into matrix 1039 | // we should not support semi specification of Matrix4, it is just weird. 1040 | 1041 | var te = this.elements; 1042 | 1043 | te[0] = ( n11 !== undefined ) ? n11 : 1; te[4] = n12 || 0; te[8] = n13 || 0; te[12] = n14 || 0; 1044 | te[1] = n21 || 0; te[5] = ( n22 !== undefined ) ? n22 : 1; te[9] = n23 || 0; te[13] = n24 || 0; 1045 | te[2] = n31 || 0; te[6] = n32 || 0; te[10] = ( n33 !== undefined ) ? n33 : 1; te[14] = n34 || 0; 1046 | te[3] = n41 || 0; te[7] = n42 || 0; te[11] = n43 || 0; te[15] = ( n44 !== undefined ) ? n44 : 1; 1047 | }; 1048 | 1049 | cornerstoneMath.Matrix4.prototype.makeRotationFromQuaternion = function(q) { 1050 | var te = this.elements; 1051 | 1052 | var x = q.x, y = q.y, z = q.z, w = q.w; 1053 | var x2 = x + x, y2 = y + y, z2 = z + z; 1054 | var xx = x * x2, xy = x * y2, xz = x * z2; 1055 | var yy = y * y2, yz = y * z2, zz = z * z2; 1056 | var wx = w * x2, wy = w * y2, wz = w * z2; 1057 | 1058 | te[0] = 1 - ( yy + zz ); 1059 | te[4] = xy - wz; 1060 | te[8] = xz + wy; 1061 | 1062 | te[1] = xy + wz; 1063 | te[5] = 1 - ( xx + zz ); 1064 | te[9] = yz - wx; 1065 | 1066 | te[2] = xz - wy; 1067 | te[6] = yz + wx; 1068 | te[10] = 1 - ( xx + yy ); 1069 | 1070 | // last column 1071 | te[3] = 0; 1072 | te[7] = 0; 1073 | te[11] = 0; 1074 | 1075 | // bottom row 1076 | te[12] = 0; 1077 | te[13] = 0; 1078 | te[14] = 0; 1079 | te[15] = 1; 1080 | 1081 | return this; 1082 | }; 1083 | 1084 | cornerstoneMath.Matrix4.prototype.multiplyMatrices = function(a, b) 1085 | { 1086 | var ae = a.elements; 1087 | var be = b.elements; 1088 | var te = this.elements; 1089 | 1090 | var a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12]; 1091 | var a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13]; 1092 | var a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14]; 1093 | var a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15]; 1094 | 1095 | var b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12]; 1096 | var b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13]; 1097 | var b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14]; 1098 | var b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15]; 1099 | 1100 | te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; 1101 | te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; 1102 | te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; 1103 | te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; 1104 | 1105 | te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; 1106 | te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; 1107 | te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; 1108 | te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; 1109 | 1110 | te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; 1111 | te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; 1112 | te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; 1113 | te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; 1114 | 1115 | te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; 1116 | te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; 1117 | te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; 1118 | te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; 1119 | 1120 | return this; 1121 | }; 1122 | 1123 | cornerstoneMath.Matrix4.prototype.multiply = function(m, n ) 1124 | { 1125 | 1126 | if (n !== undefined) { 1127 | 1128 | console.warn('DEPRECATED: Matrix4\'s .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.'); 1129 | return this.multiplyMatrices(m, n); 1130 | } 1131 | 1132 | return this.multiplyMatrices(this, m); 1133 | }; 1134 | 1135 | cornerstoneMath.Matrix4.prototype.getInverse = function ( m, throwOnInvertible ) { 1136 | 1137 | // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm 1138 | var te = this.elements; 1139 | var me = m.elements; 1140 | 1141 | var n11 = me[0], n12 = me[4], n13 = me[8], n14 = me[12]; 1142 | var n21 = me[1], n22 = me[5], n23 = me[9], n24 = me[13]; 1143 | var n31 = me[2], n32 = me[6], n33 = me[10], n34 = me[14]; 1144 | var n41 = me[3], n42 = me[7], n43 = me[11], n44 = me[15]; 1145 | 1146 | te[0] = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44; 1147 | te[4] = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44; 1148 | te[8] = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44; 1149 | te[12] = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34; 1150 | te[1] = n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44; 1151 | te[5] = n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44; 1152 | te[9] = n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44; 1153 | te[13] = n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34; 1154 | te[2] = n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44; 1155 | te[6] = n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44; 1156 | te[10] = n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44; 1157 | te[14] = n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34; 1158 | te[3] = n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43; 1159 | te[7] = n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43; 1160 | te[11] = n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43; 1161 | te[15] = n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33; 1162 | 1163 | var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ]; 1164 | 1165 | if ( det === 0 ) { 1166 | 1167 | var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0"; 1168 | 1169 | if ( throwOnInvertible || false ) { 1170 | 1171 | throw new Error( msg ); 1172 | 1173 | } else { 1174 | 1175 | console.warn( msg ); 1176 | 1177 | } 1178 | 1179 | this.identity(); 1180 | 1181 | return this; 1182 | } 1183 | 1184 | this.multiplyScalar( 1 / det ); 1185 | 1186 | return this; 1187 | 1188 | }; 1189 | 1190 | cornerstoneMath.Matrix4.prototype.applyToVector3Array = function() { 1191 | 1192 | var v1 = new cornerstoneMath.Vector3(); 1193 | 1194 | return function ( array, offset, length ) { 1195 | 1196 | if ( offset === undefined ) offset = 0; 1197 | if ( length === undefined ) length = array.length; 1198 | 1199 | for ( var i = 0, j = offset, il; i < length; i += 3, j += 3 ) { 1200 | 1201 | v1.x = array[ j ]; 1202 | v1.y = array[ j + 1 ]; 1203 | v1.z = array[ j + 2 ]; 1204 | 1205 | v1.applyMatrix4( this ); 1206 | 1207 | array[ j ] = v1.x; 1208 | array[ j + 1 ] = v1.y; 1209 | array[ j + 2 ] = v1.z; 1210 | 1211 | } 1212 | 1213 | return array; 1214 | 1215 | }; 1216 | 1217 | }; 1218 | 1219 | cornerstoneMath.Matrix4.prototype.makeTranslation = function ( x, y, z ) { 1220 | 1221 | this.set( 1222 | 1223 | 1, 0, 0, x, 1224 | 0, 1, 0, y, 1225 | 0, 0, 1, z, 1226 | 0, 0, 0, 1 1227 | 1228 | ); 1229 | 1230 | return this; 1231 | 1232 | }; 1233 | cornerstoneMath.Matrix4.prototype.multiplyScalar = function ( s ) { 1234 | 1235 | var te = this.elements; 1236 | 1237 | te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s; 1238 | te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s; 1239 | te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s; 1240 | te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s; 1241 | 1242 | return this; 1243 | 1244 | }; 1245 | cornerstoneMath.Matrix4.prototype.set = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { 1246 | 1247 | var te = this.elements; 1248 | 1249 | te[0] = n11; te[4] = n12; te[8] = n13; te[12] = n14; 1250 | te[1] = n21; te[5] = n22; te[9] = n23; te[13] = n24; 1251 | te[2] = n31; te[6] = n32; te[10] = n33; te[14] = n34; 1252 | te[3] = n41; te[7] = n42; te[11] = n43; te[15] = n44; 1253 | 1254 | return this; 1255 | 1256 | }; 1257 | 1258 | cornerstoneMath.Matrix4.prototype.makeScale = function ( x, y, z ) { 1259 | 1260 | this.set( 1261 | 1262 | x, 0, 0, 0, 1263 | 0, y, 0, 0, 1264 | 0, 0, z, 0, 1265 | 0, 0, 0, 1 1266 | 1267 | ); 1268 | 1269 | return this; 1270 | 1271 | }; 1272 | 1273 | 1274 | return cornerstoneMath; 1275 | }(cornerstoneMath)); 1276 | // End Source; src/matrix4.js 1277 | 1278 | // Begin Source: src/plane.js 1279 | // Copied from Three.JS 1280 | /** 1281 | * @author bhouston / http://exocortex.com 1282 | */ 1283 | 1284 | var cornerstoneMath = (function (cornerstoneMath) { 1285 | 1286 | "use strict"; 1287 | 1288 | if (cornerstoneMath === undefined) { 1289 | cornerstoneMath = {}; 1290 | } 1291 | 1292 | cornerstoneMath.Plane = function ( normal, constant ) { 1293 | 1294 | this.normal = ( normal !== undefined ) ? normal : new cornerstoneMath.Vector3( 1, 0, 0 ); 1295 | this.constant = ( constant !== undefined ) ? constant : 0; 1296 | 1297 | }; 1298 | 1299 | cornerstoneMath.Plane.prototype = { 1300 | 1301 | constructor: cornerstoneMath.Plane, 1302 | 1303 | set: function ( normal, constant ) { 1304 | 1305 | this.normal.copy( normal ); 1306 | this.constant = constant; 1307 | 1308 | return this; 1309 | 1310 | }, 1311 | 1312 | setComponents: function ( x, y, z, w ) { 1313 | 1314 | this.normal.set( x, y, z ); 1315 | this.constant = w; 1316 | 1317 | return this; 1318 | 1319 | }, 1320 | 1321 | setFromNormalAndCoplanarPoint: function ( normal, point ) { 1322 | 1323 | this.normal.copy( normal ); 1324 | this.constant = - point.dot( this.normal ); // must be this.normal, not normal, as this.normal is normalized 1325 | 1326 | return this; 1327 | 1328 | }, 1329 | 1330 | setFromCoplanarPoints: function () { 1331 | 1332 | var v1 = new cornerstoneMath.Vector3(); 1333 | var v2 = new cornerstoneMath.Vector3(); 1334 | 1335 | return function ( a, b, c ) { 1336 | 1337 | var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize(); 1338 | 1339 | // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? 1340 | 1341 | this.setFromNormalAndCoplanarPoint( normal, a ); 1342 | 1343 | return this; 1344 | 1345 | }; 1346 | 1347 | }(), 1348 | 1349 | 1350 | copy: function ( plane ) { 1351 | 1352 | this.normal.copy( plane.normal ); 1353 | this.constant = plane.constant; 1354 | 1355 | return this; 1356 | 1357 | }, 1358 | 1359 | normalize: function () { 1360 | 1361 | // Note: will lead to a divide by zero if the plane is invalid. 1362 | 1363 | var inverseNormalLength = 1.0 / this.normal.length(); 1364 | this.normal.multiplyScalar( inverseNormalLength ); 1365 | this.constant *= inverseNormalLength; 1366 | 1367 | return this; 1368 | 1369 | }, 1370 | 1371 | negate: function () { 1372 | 1373 | this.constant *= - 1; 1374 | this.normal.negate(); 1375 | 1376 | return this; 1377 | 1378 | }, 1379 | 1380 | distanceToPoint: function ( point ) { 1381 | 1382 | return this.normal.dot( point ) + this.constant; 1383 | 1384 | }, 1385 | 1386 | distanceToSphere: function ( sphere ) { 1387 | 1388 | return this.distanceToPoint( sphere.center ) - sphere.radius; 1389 | 1390 | }, 1391 | 1392 | projectPoint: function ( point, optionalTarget ) { 1393 | 1394 | return this.orthoPoint( point, optionalTarget ).sub( point ).negate(); 1395 | 1396 | }, 1397 | 1398 | orthoPoint: function ( point, optionalTarget ) { 1399 | 1400 | var perpendicularMagnitude = this.distanceToPoint( point ); 1401 | 1402 | var result = optionalTarget || new cornerstoneMath.Vector3(); 1403 | return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude ); 1404 | 1405 | }, 1406 | 1407 | isIntersectionLine: function ( line ) { 1408 | 1409 | // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. 1410 | 1411 | var startSign = this.distanceToPoint( line.start ); 1412 | var endSign = this.distanceToPoint( line.end ); 1413 | 1414 | return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); 1415 | 1416 | }, 1417 | 1418 | intersectLine: function () { 1419 | 1420 | var v1 = new cornerstoneMath.Vector3(); 1421 | 1422 | return function ( line, optionalTarget ) { 1423 | 1424 | var result = optionalTarget || new cornerstoneMath.Vector3(); 1425 | 1426 | var direction = line.delta( v1 ); 1427 | 1428 | var denominator = this.normal.dot( direction ); 1429 | 1430 | if ( denominator === 0 ) { 1431 | 1432 | // line is coplanar, return origin 1433 | if ( this.distanceToPoint( line.start ) === 0 ) { 1434 | 1435 | return result.copy( line.start ); 1436 | 1437 | } 1438 | 1439 | // Unsure if this is the correct method to handle this case. 1440 | return undefined; 1441 | 1442 | } 1443 | 1444 | var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; 1445 | 1446 | if ( t < 0 || t > 1 ) { 1447 | 1448 | return undefined; 1449 | 1450 | } 1451 | 1452 | return result.copy( direction ).multiplyScalar( t ).add( line.start ); 1453 | 1454 | }; 1455 | 1456 | }(), 1457 | 1458 | intersectPlane: function (targetPlane) { 1459 | // Returns the intersection line between two planes 1460 | var direction = this.normal.clone().cross(targetPlane.normal); 1461 | var origin = new cornerstoneMath.Vector3(); 1462 | var intersectionData = { 1463 | origin: origin, 1464 | direction: direction 1465 | }; 1466 | 1467 | // If the planes are parallel, return an empty vector for the 1468 | // intersection line 1469 | if (this.normal.clone().cross(targetPlane.normal).length < 1e-10) { 1470 | intersectionData.direction = new cornerstoneMath.Vector3(); 1471 | return intersectionData; 1472 | } 1473 | 1474 | var h1 = this.constant; 1475 | var h2 = targetPlane.constant; 1476 | var n1dotn2 = this.normal.clone().dot(targetPlane.normal); 1477 | 1478 | var c1 = -(h1 - h2 * n1dotn2) / (1 - n1dotn2 * n1dotn2); 1479 | var c2 = -(h2 - h1 * n1dotn2) / (1 - n1dotn2 * n1dotn2); 1480 | intersectionData.origin = this.normal.clone().multiplyScalar(c1).add(targetPlane.normal.clone().multiplyScalar(c2)); 1481 | return intersectionData; 1482 | }, 1483 | 1484 | coplanarPoint: function ( optionalTarget ) { 1485 | 1486 | var result = optionalTarget || new cornerstoneMath.Vector3(); 1487 | return result.copy( this.normal ).multiplyScalar( - this.constant ); 1488 | 1489 | }, 1490 | 1491 | translate: function ( offset ) { 1492 | 1493 | this.constant = this.constant - offset.dot( this.normal ); 1494 | 1495 | return this; 1496 | 1497 | }, 1498 | 1499 | equals: function ( plane ) { 1500 | 1501 | return plane.normal.equals( this.normal ) && ( plane.constant == this.constant ); 1502 | 1503 | }, 1504 | 1505 | clone: function () { 1506 | 1507 | return new cornerstoneMath.Plane().copy( this ); 1508 | 1509 | } 1510 | }; 1511 | 1512 | return cornerstoneMath; 1513 | }(cornerstoneMath)); 1514 | // End Source; src/plane.js 1515 | 1516 | // Begin Source: src/point.js 1517 | var cornerstoneMath = (function (cornerstoneMath) { 1518 | 1519 | "use strict"; 1520 | 1521 | if(cornerstoneMath === undefined) { 1522 | cornerstoneMath = {}; 1523 | } 1524 | 1525 | function pageToPoint(e) 1526 | { 1527 | return { 1528 | x : e.pageX, 1529 | y : e.pageY 1530 | }; 1531 | } 1532 | 1533 | function subtract(lhs, rhs) 1534 | { 1535 | return { 1536 | x : lhs.x - rhs.x, 1537 | y : lhs.y - rhs.y 1538 | }; 1539 | } 1540 | 1541 | function copy(point) 1542 | { 1543 | return { 1544 | x : point.x, 1545 | y : point.y 1546 | }; 1547 | } 1548 | 1549 | function distance(from, to) 1550 | { 1551 | return Math.sqrt(distanceSquared(from, to)); 1552 | } 1553 | 1554 | function distanceSquared(from, to) 1555 | { 1556 | var delta = subtract(from, to); 1557 | return delta.x * delta.x + delta.y * delta.y; 1558 | } 1559 | 1560 | function insideRect(point, rect) 1561 | { 1562 | if( point.x < rect.left || 1563 | point.x > rect.left + rect.width || 1564 | point.y < rect.top || 1565 | point.y > rect.top + rect.height) 1566 | { 1567 | return false; 1568 | } 1569 | return true; 1570 | } 1571 | 1572 | 1573 | // module exports 1574 | cornerstoneMath.point = 1575 | { 1576 | subtract : subtract, 1577 | copy: copy, 1578 | pageToPoint: pageToPoint, 1579 | distance: distance, 1580 | distanceSquared: distanceSquared, 1581 | insideRect: insideRect 1582 | }; 1583 | 1584 | 1585 | return cornerstoneMath; 1586 | }(cornerstoneMath)); 1587 | // End Source; src/point.js 1588 | 1589 | // Begin Source: src/quaternion.js 1590 | // Based on THREE.JS 1591 | 1592 | var cornerstoneMath = (function (cornerstoneMath) { 1593 | 1594 | "use strict"; 1595 | 1596 | if(cornerstoneMath === undefined) { 1597 | cornerstoneMath = {}; 1598 | } 1599 | 1600 | cornerstoneMath.Quaternion = function Quaternion(x, y, z, w) 1601 | { 1602 | this.x = x || 0; 1603 | this.y = y || 0; 1604 | this.z = z || 0; 1605 | this.w = ( w !== undefined ) ? w : 1; 1606 | }; 1607 | 1608 | cornerstoneMath.Quaternion.prototype.setFromAxisAngle = function(axis, angle) 1609 | { 1610 | var halfAngle = angle / 2, s = Math.sin( halfAngle ); 1611 | 1612 | this.x = axis.x * s; 1613 | this.y = axis.y * s; 1614 | this.z = axis.z * s; 1615 | this.w = Math.cos( halfAngle ); 1616 | 1617 | return this; 1618 | }; 1619 | 1620 | cornerstoneMath.Quaternion.prototype.multiplyQuaternions = function( a, b) 1621 | { 1622 | var qax = a.x, qay = a.y, qaz = a.z, qaw = a.w; 1623 | var qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w; 1624 | 1625 | this.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; 1626 | this.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; 1627 | this.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; 1628 | this.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; 1629 | 1630 | return this; 1631 | }; 1632 | 1633 | cornerstoneMath.Quaternion.prototype.setFromRotationMatrix = function(m) 1634 | { 1635 | var te = m.elements, 1636 | 1637 | m11 = te[0], m12 = te[4], m13 = te[8], 1638 | m21 = te[1], m22 = te[5], m23 = te[9], 1639 | m31 = te[2], m32 = te[6], m33 = te[10], 1640 | 1641 | trace = m11 + m22 + m33, 1642 | s; 1643 | 1644 | if ( trace > 0 ) { 1645 | 1646 | s = 0.5 / Math.sqrt( trace + 1.0 ); 1647 | 1648 | this.w = 0.25 / s; 1649 | this.x = ( m32 - m23 ) * s; 1650 | this.y = ( m13 - m31 ) * s; 1651 | this.z = ( m21 - m12 ) * s; 1652 | 1653 | } else if ( m11 > m22 && m11 > m33 ) { 1654 | 1655 | s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); 1656 | 1657 | this.w = (m32 - m23 ) / s; 1658 | this.x = 0.25 * s; 1659 | this.y = (m12 + m21 ) / s; 1660 | this.z = (m13 + m31 ) / s; 1661 | 1662 | } else if ( m22 > m33 ) { 1663 | 1664 | s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); 1665 | 1666 | this.w = (m13 - m31 ) / s; 1667 | this.x = (m12 + m21 ) / s; 1668 | this.y = 0.25 * s; 1669 | this.z = (m23 + m32 ) / s; 1670 | 1671 | } else { 1672 | 1673 | s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); 1674 | 1675 | this.w = ( m21 - m12 ) / s; 1676 | this.x = ( m13 + m31 ) / s; 1677 | this.y = ( m23 + m32 ) / s; 1678 | this.z = 0.25 * s; 1679 | 1680 | } 1681 | 1682 | return this; 1683 | }; 1684 | 1685 | return cornerstoneMath; 1686 | }(cornerstoneMath)); 1687 | // End Source; src/quaternion.js 1688 | 1689 | // Begin Source: src/rect.js 1690 | var cornerstoneMath = (function (cornerstoneMath) { 1691 | 1692 | "use strict"; 1693 | 1694 | if(cornerstoneMath === undefined) { 1695 | cornerstoneMath = {}; 1696 | } 1697 | 1698 | function rectToLineSegments(rect) 1699 | { 1700 | var top = { 1701 | start : { 1702 | x :rect.left, 1703 | y :rect.top 1704 | }, 1705 | end : { 1706 | x :rect.left + rect.width, 1707 | y :rect.top 1708 | 1709 | } 1710 | }; 1711 | var right = { 1712 | start : { 1713 | x :rect.left + rect.width, 1714 | y :rect.top 1715 | }, 1716 | end : { 1717 | x :rect.left + rect.width, 1718 | y :rect.top + rect.height 1719 | 1720 | } 1721 | }; 1722 | var bottom = { 1723 | start : { 1724 | x :rect.left + rect.width, 1725 | y :rect.top + rect.height 1726 | }, 1727 | end : { 1728 | x :rect.left, 1729 | y :rect.top + rect.height 1730 | 1731 | } 1732 | }; 1733 | var left = { 1734 | start : { 1735 | x :rect.left, 1736 | y :rect.top + rect.height 1737 | }, 1738 | end : { 1739 | x :rect.left, 1740 | y :rect.top 1741 | 1742 | } 1743 | }; 1744 | var lineSegments = [top, right, bottom, left]; 1745 | return lineSegments; 1746 | } 1747 | 1748 | function pointNearLineSegment(point, lineSegment, maxDistance) 1749 | { 1750 | if(maxDistance === undefined) { 1751 | maxDistance = 5; 1752 | } 1753 | var distance = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, point); 1754 | 1755 | return (distance < maxDistance); 1756 | } 1757 | function distanceToPoint(rect, point) 1758 | { 1759 | var minDistance = 655535; 1760 | var lineSegments = rectToLineSegments(rect); 1761 | lineSegments.forEach(function(lineSegment) { 1762 | var distance = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, point); 1763 | if(distance < minDistance) { 1764 | minDistance = distance; 1765 | } 1766 | }); 1767 | return minDistance; 1768 | } 1769 | 1770 | // module exports 1771 | cornerstoneMath.rect = 1772 | { 1773 | distanceToPoint : distanceToPoint 1774 | }; 1775 | 1776 | 1777 | return cornerstoneMath; 1778 | }(cornerstoneMath)); 1779 | // End Source; src/rect.js 1780 | -------------------------------------------------------------------------------- /client/compatibility/hammer.js: -------------------------------------------------------------------------------- 1 | /*! Hammer.JS - v2.0.4 - 2015-09-25 2 | * http://hammerjs.github.io/ 3 | * 4 | * Copyright (c) 2015 Jorik Tangelder; 5 | * Licensed under the license */ 6 | (function(window, document, exportName, undefined) { 7 | 'use strict'; 8 | 9 | var VENDOR_PREFIXES = ['', 'webkit', 'moz', 'MS', 'ms', 'o']; 10 | var TEST_ELEMENT = document.createElement('div'); 11 | 12 | var TYPE_FUNCTION = 'function'; 13 | 14 | var round = Math.round; 15 | var abs = Math.abs; 16 | var now = Date.now; 17 | 18 | /** 19 | * set a timeout with a given scope 20 | * @param {Function} fn 21 | * @param {Number} timeout 22 | * @param {Object} context 23 | * @returns {number} 24 | */ 25 | function setTimeoutContext(fn, timeout, context) { 26 | return setTimeout(bindFn(fn, context), timeout); 27 | } 28 | 29 | /** 30 | * if the argument is an array, we want to execute the fn on each entry 31 | * if it aint an array we don't want to do a thing. 32 | * this is used by all the methods that accept a single and array argument. 33 | * @param {*|Array} arg 34 | * @param {String} fn 35 | * @param {Object} [context] 36 | * @returns {Boolean} 37 | */ 38 | function invokeArrayArg(arg, fn, context) { 39 | if (Array.isArray(arg)) { 40 | each(arg, context[fn], context); 41 | return true; 42 | } 43 | return false; 44 | } 45 | 46 | /** 47 | * walk objects and arrays 48 | * @param {Object} obj 49 | * @param {Function} iterator 50 | * @param {Object} context 51 | */ 52 | function each(obj, iterator, context) { 53 | var i; 54 | 55 | if (!obj) { 56 | return; 57 | } 58 | 59 | if (obj.forEach) { 60 | obj.forEach(iterator, context); 61 | } else if (obj.length !== undefined) { 62 | i = 0; 63 | while (i < obj.length) { 64 | iterator.call(context, obj[i], i, obj); 65 | i++; 66 | } 67 | } else { 68 | for (i in obj) { 69 | obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * extend object. 76 | * means that properties in dest will be overwritten by the ones in src. 77 | * @param {Object} dest 78 | * @param {Object} src 79 | * @param {Boolean} [merge] 80 | * @returns {Object} dest 81 | */ 82 | function extend(dest, src, merge) { 83 | var keys = Object.keys(src); 84 | var i = 0; 85 | while (i < keys.length) { 86 | if (!merge || (merge && dest[keys[i]] === undefined)) { 87 | dest[keys[i]] = src[keys[i]]; 88 | } 89 | i++; 90 | } 91 | return dest; 92 | } 93 | 94 | /** 95 | * merge the values from src in the dest. 96 | * means that properties that exist in dest will not be overwritten by src 97 | * @param {Object} dest 98 | * @param {Object} src 99 | * @returns {Object} dest 100 | */ 101 | function merge(dest, src) { 102 | return extend(dest, src, true); 103 | } 104 | 105 | /** 106 | * simple class inheritance 107 | * @param {Function} child 108 | * @param {Function} base 109 | * @param {Object} [properties] 110 | */ 111 | function inherit(child, base, properties) { 112 | var baseP = base.prototype, 113 | childP; 114 | 115 | childP = child.prototype = Object.create(baseP); 116 | childP.constructor = child; 117 | childP._super = baseP; 118 | 119 | if (properties) { 120 | extend(childP, properties); 121 | } 122 | } 123 | 124 | /** 125 | * simple function bind 126 | * @param {Function} fn 127 | * @param {Object} context 128 | * @returns {Function} 129 | */ 130 | function bindFn(fn, context) { 131 | return function boundFn() { 132 | return fn.apply(context, arguments); 133 | }; 134 | } 135 | 136 | /** 137 | * let a boolean value also be a function that must return a boolean 138 | * this first item in args will be used as the context 139 | * @param {Boolean|Function} val 140 | * @param {Array} [args] 141 | * @returns {Boolean} 142 | */ 143 | function boolOrFn(val, args) { 144 | if (typeof val == TYPE_FUNCTION) { 145 | return val.apply(args ? args[0] || undefined : undefined, args); 146 | } 147 | return val; 148 | } 149 | 150 | /** 151 | * use the val2 when val1 is undefined 152 | * @param {*} val1 153 | * @param {*} val2 154 | * @returns {*} 155 | */ 156 | function ifUndefined(val1, val2) { 157 | return (val1 === undefined) ? val2 : val1; 158 | } 159 | 160 | /** 161 | * addEventListener with multiple events at once 162 | * @param {EventTarget} target 163 | * @param {String} types 164 | * @param {Function} handler 165 | */ 166 | function addEventListeners(target, types, handler) { 167 | each(splitStr(types), function(type) { 168 | target.addEventListener(type, handler, false); 169 | }); 170 | } 171 | 172 | /** 173 | * removeEventListener with multiple events at once 174 | * @param {EventTarget} target 175 | * @param {String} types 176 | * @param {Function} handler 177 | */ 178 | function removeEventListeners(target, types, handler) { 179 | each(splitStr(types), function(type) { 180 | target.removeEventListener(type, handler, false); 181 | }); 182 | } 183 | 184 | /** 185 | * find if a node is in the given parent 186 | * @method hasParent 187 | * @param {HTMLElement} node 188 | * @param {HTMLElement} parent 189 | * @return {Boolean} found 190 | */ 191 | function hasParent(node, parent) { 192 | while (node) { 193 | if (node == parent) { 194 | return true; 195 | } 196 | node = node.parentNode; 197 | } 198 | return false; 199 | } 200 | 201 | /** 202 | * small indexOf wrapper 203 | * @param {String} str 204 | * @param {String} find 205 | * @returns {Boolean} found 206 | */ 207 | function inStr(str, find) { 208 | return str.indexOf(find) > -1; 209 | } 210 | 211 | /** 212 | * split string on whitespace 213 | * @param {String} str 214 | * @returns {Array} words 215 | */ 216 | function splitStr(str) { 217 | return str.trim().split(/\s+/g); 218 | } 219 | 220 | /** 221 | * find if a array contains the object using indexOf or a simple polyFill 222 | * @param {Array} src 223 | * @param {String} find 224 | * @param {String} [findByKey] 225 | * @return {Boolean|Number} false when not found, or the index 226 | */ 227 | function inArray(src, find, findByKey) { 228 | if (src.indexOf && !findByKey) { 229 | return src.indexOf(find); 230 | } else { 231 | var i = 0; 232 | while (i < src.length) { 233 | if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) { 234 | return i; 235 | } 236 | i++; 237 | } 238 | return -1; 239 | } 240 | } 241 | 242 | /** 243 | * convert array-like objects to real arrays 244 | * @param {Object} obj 245 | * @returns {Array} 246 | */ 247 | function toArray(obj) { 248 | return Array.prototype.slice.call(obj, 0); 249 | } 250 | 251 | /** 252 | * unique array with objects based on a key (like 'id') or just by the array's value 253 | * @param {Array} src [{id:1},{id:2},{id:1}] 254 | * @param {String} [key] 255 | * @param {Boolean} [sort=False] 256 | * @returns {Array} [{id:1},{id:2}] 257 | */ 258 | function uniqueArray(src, key, sort) { 259 | var results = []; 260 | var values = []; 261 | var i = 0; 262 | 263 | while (i < src.length) { 264 | var val = key ? src[i][key] : src[i]; 265 | if (inArray(values, val) < 0) { 266 | results.push(src[i]); 267 | } 268 | values[i] = val; 269 | i++; 270 | } 271 | 272 | if (sort) { 273 | if (!key) { 274 | results = results.sort(); 275 | } else { 276 | results = results.sort(function sortUniqueArray(a, b) { 277 | return a[key] > b[key]; 278 | }); 279 | } 280 | } 281 | 282 | return results; 283 | } 284 | 285 | /** 286 | * get the prefixed property 287 | * @param {Object} obj 288 | * @param {String} property 289 | * @returns {String|Undefined} prefixed 290 | */ 291 | function prefixed(obj, property) { 292 | var prefix, prop; 293 | var camelProp = property[0].toUpperCase() + property.slice(1); 294 | 295 | var i = 0; 296 | while (i < VENDOR_PREFIXES.length) { 297 | prefix = VENDOR_PREFIXES[i]; 298 | prop = (prefix) ? prefix + camelProp : property; 299 | 300 | if (prop in obj) { 301 | return prop; 302 | } 303 | i++; 304 | } 305 | return undefined; 306 | } 307 | 308 | /** 309 | * get a unique id 310 | * @returns {number} uniqueId 311 | */ 312 | var _uniqueId = 1; 313 | function uniqueId() { 314 | return _uniqueId++; 315 | } 316 | 317 | /** 318 | * get the window object of an element 319 | * @param {HTMLElement} element 320 | * @returns {DocumentView|Window} 321 | */ 322 | function getWindowForElement(element) { 323 | var doc = element.ownerDocument || element; 324 | return (doc.defaultView || doc.parentWindow || window); 325 | } 326 | 327 | var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; 328 | 329 | var SUPPORT_TOUCH = ('ontouchstart' in window); 330 | var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; 331 | var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent); 332 | 333 | var INPUT_TYPE_TOUCH = 'touch'; 334 | var INPUT_TYPE_PEN = 'pen'; 335 | var INPUT_TYPE_MOUSE = 'mouse'; 336 | var INPUT_TYPE_KINECT = 'kinect'; 337 | 338 | var COMPUTE_INTERVAL = 25; 339 | 340 | var INPUT_START = 1; 341 | var INPUT_MOVE = 2; 342 | var INPUT_END = 4; 343 | var INPUT_CANCEL = 8; 344 | 345 | var DIRECTION_NONE = 1; 346 | var DIRECTION_LEFT = 2; 347 | var DIRECTION_RIGHT = 4; 348 | var DIRECTION_UP = 8; 349 | var DIRECTION_DOWN = 16; 350 | 351 | var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; 352 | var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; 353 | var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; 354 | 355 | var PROPS_XY = ['x', 'y']; 356 | var PROPS_CLIENT_XY = ['clientX', 'clientY']; 357 | 358 | /** 359 | * create new input type manager 360 | * @param {Manager} manager 361 | * @param {Function} callback 362 | * @returns {Input} 363 | * @constructor 364 | */ 365 | function Input(manager, callback) { 366 | var self = this; 367 | this.manager = manager; 368 | this.callback = callback; 369 | this.element = manager.element; 370 | this.target = manager.options.inputTarget; 371 | 372 | // smaller wrapper around the handler, for the scope and the enabled state of the manager, 373 | // so when disabled the input events are completely bypassed. 374 | this.domHandler = function(ev) { 375 | if (boolOrFn(manager.options.enable, [manager])) { 376 | self.handler(ev); 377 | } 378 | }; 379 | 380 | this.init(); 381 | 382 | } 383 | 384 | Input.prototype = { 385 | /** 386 | * should handle the inputEvent data and trigger the callback 387 | * @virtual 388 | */ 389 | handler: function() { }, 390 | 391 | /** 392 | * bind the events 393 | */ 394 | init: function() { 395 | this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); 396 | this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); 397 | this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); 398 | }, 399 | 400 | /** 401 | * unbind the events 402 | */ 403 | destroy: function() { 404 | this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler); 405 | this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler); 406 | this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); 407 | } 408 | }; 409 | 410 | /** 411 | * create new input type manager 412 | * called by the Manager constructor 413 | * @param {Hammer} manager 414 | * @returns {Input} 415 | */ 416 | function createInputInstance(manager) { 417 | var Type; 418 | var inputClass = manager.options.inputClass; 419 | 420 | if (inputClass) { 421 | Type = inputClass; 422 | } else if (SUPPORT_POINTER_EVENTS) { 423 | Type = PointerEventInput; 424 | } else if (SUPPORT_ONLY_TOUCH) { 425 | Type = TouchInput; 426 | } else if (!SUPPORT_TOUCH) { 427 | Type = MouseInput; 428 | } else { 429 | Type = TouchMouseInput; 430 | } 431 | return new (Type)(manager, inputHandler); 432 | } 433 | 434 | /** 435 | * handle input events 436 | * @param {Manager} manager 437 | * @param {String} eventType 438 | * @param {Object} input 439 | */ 440 | function inputHandler(manager, eventType, input) { 441 | var pointersLen = input.pointers.length; 442 | var changedPointersLen = input.changedPointers.length; 443 | var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0)); 444 | var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0)); 445 | 446 | input.isFirst = !!isFirst; 447 | input.isFinal = !!isFinal; 448 | 449 | if (isFirst) { 450 | manager.session = {}; 451 | } 452 | 453 | // source event is the normalized value of the domEvents 454 | // like 'touchstart, mouseup, pointerdown' 455 | input.eventType = eventType; 456 | 457 | // compute scale, rotation etc 458 | computeInputData(manager, input); 459 | 460 | // emit secret event 461 | manager.emit('hammer.input', input); 462 | 463 | manager.recognize(input); 464 | manager.session.prevInput = input; 465 | } 466 | 467 | /** 468 | * extend the data with some usable properties like scale, rotate, velocity etc 469 | * @param {Object} manager 470 | * @param {Object} input 471 | */ 472 | function computeInputData(manager, input) { 473 | var session = manager.session; 474 | var pointers = input.pointers; 475 | var pointersLength = pointers.length; 476 | 477 | // store the first input to calculate the distance and direction 478 | if (!session.firstInput) { 479 | session.firstInput = simpleCloneInputData(input); 480 | } 481 | 482 | // to compute scale and rotation we need to store the multiple touches 483 | if (pointersLength > 1 && !session.firstMultiple) { 484 | session.firstMultiple = simpleCloneInputData(input); 485 | } else if (pointersLength === 1) { 486 | session.firstMultiple = false; 487 | } 488 | 489 | var firstInput = session.firstInput; 490 | var firstMultiple = session.firstMultiple; 491 | var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center; 492 | 493 | var center = input.center = getCenter(pointers); 494 | input.timeStamp = now(); 495 | input.deltaTime = input.timeStamp - firstInput.timeStamp; 496 | 497 | input.angle = getAngle(offsetCenter, center); 498 | input.distance = getDistance(offsetCenter, center); 499 | 500 | computeDeltaXY(session, input); 501 | input.offsetDirection = getDirection(input.deltaX, input.deltaY); 502 | 503 | var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY); 504 | input.overallVelocityX = overallVelocity.x; 505 | input.overallVelocityY = overallVelocity.y; 506 | input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y; 507 | 508 | input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; 509 | input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; 510 | 511 | input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length > 512 | session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers); 513 | 514 | computeIntervalInputData(session, input); 515 | 516 | // find the correct target 517 | var target = manager.element; 518 | if (hasParent(input.srcEvent.target, target)) { 519 | target = input.srcEvent.target; 520 | } 521 | input.target = target; 522 | } 523 | 524 | function computeDeltaXY(session, input) { 525 | var center = input.center; 526 | var offset = session.offsetDelta || {}; 527 | var prevDelta = session.prevDelta || {}; 528 | var prevInput = session.prevInput || {}; 529 | 530 | if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { 531 | prevDelta = session.prevDelta = { 532 | x: prevInput.deltaX || 0, 533 | y: prevInput.deltaY || 0 534 | }; 535 | 536 | offset = session.offsetDelta = { 537 | x: center.x, 538 | y: center.y 539 | }; 540 | } 541 | 542 | input.deltaX = prevDelta.x + (center.x - offset.x); 543 | input.deltaY = prevDelta.y + (center.y - offset.y); 544 | } 545 | 546 | /** 547 | * velocity is calculated every x ms 548 | * @param {Object} session 549 | * @param {Object} input 550 | */ 551 | function computeIntervalInputData(session, input) { 552 | var last = session.lastInterval || input, 553 | deltaTime = input.timeStamp - last.timeStamp, 554 | velocity, velocityX, velocityY, direction; 555 | 556 | if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) { 557 | var deltaX = input.deltaX - last.deltaX; 558 | var deltaY = input.deltaY - last.deltaY; 559 | 560 | var v = getVelocity(deltaTime, deltaX, deltaY); 561 | velocityX = v.x; 562 | velocityY = v.y; 563 | velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y; 564 | direction = getDirection(deltaX, deltaY); 565 | 566 | session.lastInterval = input; 567 | } else { 568 | // use latest velocity info if it doesn't overtake a minimum period 569 | velocity = last.velocity; 570 | velocityX = last.velocityX; 571 | velocityY = last.velocityY; 572 | direction = last.direction; 573 | } 574 | 575 | input.velocity = velocity; 576 | input.velocityX = velocityX; 577 | input.velocityY = velocityY; 578 | input.direction = direction; 579 | } 580 | 581 | /** 582 | * create a simple clone from the input used for storage of firstInput and firstMultiple 583 | * @param {Object} input 584 | * @returns {Object} clonedInputData 585 | */ 586 | function simpleCloneInputData(input) { 587 | // make a simple copy of the pointers because we will get a reference if we don't 588 | // we only need clientXY for the calculations 589 | var pointers = []; 590 | var i = 0; 591 | while (i < input.pointers.length) { 592 | pointers[i] = { 593 | clientX: round(input.pointers[i].clientX), 594 | clientY: round(input.pointers[i].clientY) 595 | }; 596 | i++; 597 | } 598 | 599 | return { 600 | timeStamp: now(), 601 | pointers: pointers, 602 | center: getCenter(pointers), 603 | deltaX: input.deltaX, 604 | deltaY: input.deltaY 605 | }; 606 | } 607 | 608 | /** 609 | * get the center of all the pointers 610 | * @param {Array} pointers 611 | * @return {Object} center contains `x` and `y` properties 612 | */ 613 | function getCenter(pointers) { 614 | var pointersLength = pointers.length; 615 | 616 | // no need to loop when only one touch 617 | if (pointersLength === 1) { 618 | return { 619 | x: round(pointers[0].clientX), 620 | y: round(pointers[0].clientY) 621 | }; 622 | } 623 | 624 | var x = 0, y = 0, i = 0; 625 | while (i < pointersLength) { 626 | x += pointers[i].clientX; 627 | y += pointers[i].clientY; 628 | i++; 629 | } 630 | 631 | return { 632 | x: round(x / pointersLength), 633 | y: round(y / pointersLength) 634 | }; 635 | } 636 | 637 | /** 638 | * calculate the velocity between two points. unit is in px per ms. 639 | * @param {Number} deltaTime 640 | * @param {Number} x 641 | * @param {Number} y 642 | * @return {Object} velocity `x` and `y` 643 | */ 644 | function getVelocity(deltaTime, x, y) { 645 | return { 646 | x: x / deltaTime || 0, 647 | y: y / deltaTime || 0 648 | }; 649 | } 650 | 651 | /** 652 | * get the direction between two points 653 | * @param {Number} x 654 | * @param {Number} y 655 | * @return {Number} direction 656 | */ 657 | function getDirection(x, y) { 658 | if (x === y) { 659 | return DIRECTION_NONE; 660 | } 661 | 662 | if (abs(x) >= abs(y)) { 663 | return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; 664 | } 665 | return y < 0 ? DIRECTION_UP : DIRECTION_DOWN; 666 | } 667 | 668 | /** 669 | * calculate the absolute distance between two points 670 | * @param {Object} p1 {x, y} 671 | * @param {Object} p2 {x, y} 672 | * @param {Array} [props] containing x and y keys 673 | * @return {Number} distance 674 | */ 675 | function getDistance(p1, p2, props) { 676 | if (!props) { 677 | props = PROPS_XY; 678 | } 679 | var x = p2[props[0]] - p1[props[0]], 680 | y = p2[props[1]] - p1[props[1]]; 681 | 682 | return Math.sqrt((x * x) + (y * y)); 683 | } 684 | 685 | /** 686 | * calculate the angle between two coordinates 687 | * @param {Object} p1 688 | * @param {Object} p2 689 | * @param {Array} [props] containing x and y keys 690 | * @return {Number} angle 691 | */ 692 | function getAngle(p1, p2, props) { 693 | if (!props) { 694 | props = PROPS_XY; 695 | } 696 | var x = p2[props[0]] - p1[props[0]], 697 | y = p2[props[1]] - p1[props[1]]; 698 | return Math.atan2(y, x) * 180 / Math.PI; 699 | } 700 | 701 | /** 702 | * calculate the rotation degrees between two pointersets 703 | * @param {Array} start array of pointers 704 | * @param {Array} end array of pointers 705 | * @return {Number} rotation 706 | */ 707 | function getRotation(start, end) { 708 | return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY); 709 | } 710 | 711 | /** 712 | * calculate the scale factor between two pointersets 713 | * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out 714 | * @param {Array} start array of pointers 715 | * @param {Array} end array of pointers 716 | * @return {Number} scale 717 | */ 718 | function getScale(start, end) { 719 | return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY); 720 | } 721 | 722 | var MOUSE_INPUT_MAP = { 723 | mousedown: INPUT_START, 724 | mousemove: INPUT_MOVE, 725 | mouseup: INPUT_END 726 | }; 727 | 728 | var MOUSE_ELEMENT_EVENTS = 'mousedown'; 729 | var MOUSE_WINDOW_EVENTS = 'mousemove mouseup'; 730 | 731 | /** 732 | * Mouse events input 733 | * @constructor 734 | * @extends Input 735 | */ 736 | function MouseInput() { 737 | this.evEl = MOUSE_ELEMENT_EVENTS; 738 | this.evWin = MOUSE_WINDOW_EVENTS; 739 | 740 | this.allow = true; // used by Input.TouchMouse to disable mouse events 741 | this.pressed = false; // mousedown state 742 | 743 | Input.apply(this, arguments); 744 | } 745 | 746 | inherit(MouseInput, Input, { 747 | /** 748 | * handle mouse events 749 | * @param {Object} ev 750 | */ 751 | handler: function MEhandler(ev) { 752 | var eventType = MOUSE_INPUT_MAP[ev.type]; 753 | 754 | // on start we want to have the left mouse button down 755 | if (eventType & INPUT_START && ev.button === 0) { 756 | this.pressed = true; 757 | } 758 | 759 | if (eventType & INPUT_MOVE && ev.which !== 1) { 760 | eventType = INPUT_END; 761 | } 762 | 763 | // mouse must be down, and mouse events are allowed (see the TouchMouse input) 764 | if (!this.pressed || !this.allow) { 765 | return; 766 | } 767 | 768 | if (eventType & INPUT_END) { 769 | this.pressed = false; 770 | } 771 | 772 | this.callback(this.manager, eventType, { 773 | pointers: [ev], 774 | changedPointers: [ev], 775 | pointerType: INPUT_TYPE_MOUSE, 776 | srcEvent: ev 777 | }); 778 | } 779 | }); 780 | 781 | var POINTER_INPUT_MAP = { 782 | pointerdown: INPUT_START, 783 | pointermove: INPUT_MOVE, 784 | pointerup: INPUT_END, 785 | pointercancel: INPUT_CANCEL, 786 | pointerout: INPUT_CANCEL 787 | }; 788 | 789 | // in IE10 the pointer types is defined as an enum 790 | var IE10_POINTER_TYPE_ENUM = { 791 | 2: INPUT_TYPE_TOUCH, 792 | 3: INPUT_TYPE_PEN, 793 | 4: INPUT_TYPE_MOUSE, 794 | 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816 795 | }; 796 | 797 | var POINTER_ELEMENT_EVENTS = 'pointerdown'; 798 | var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; 799 | 800 | // IE10 has prefixed support, and case-sensitive 801 | if (window.MSPointerEvent) { 802 | POINTER_ELEMENT_EVENTS = 'MSPointerDown'; 803 | POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; 804 | } 805 | 806 | /** 807 | * Pointer events input 808 | * @constructor 809 | * @extends Input 810 | */ 811 | function PointerEventInput() { 812 | this.evEl = POINTER_ELEMENT_EVENTS; 813 | this.evWin = POINTER_WINDOW_EVENTS; 814 | 815 | Input.apply(this, arguments); 816 | 817 | this.store = (this.manager.session.pointerEvents = []); 818 | } 819 | 820 | inherit(PointerEventInput, Input, { 821 | /** 822 | * handle mouse events 823 | * @param {Object} ev 824 | */ 825 | handler: function PEhandler(ev) { 826 | var store = this.store; 827 | var removePointer = false; 828 | 829 | var eventTypeNormalized = ev.type.toLowerCase().replace('ms', ''); 830 | var eventType = POINTER_INPUT_MAP[eventTypeNormalized]; 831 | var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType; 832 | 833 | var isTouch = (pointerType == INPUT_TYPE_TOUCH); 834 | 835 | // get index of the event in the store 836 | var storeIndex = inArray(store, ev.pointerId, 'pointerId'); 837 | 838 | // start and mouse must be down 839 | if (eventType & INPUT_START && (ev.button === 0 || isTouch)) { 840 | if (storeIndex < 0) { 841 | store.push(ev); 842 | storeIndex = store.length - 1; 843 | } 844 | } else if (eventType & (INPUT_END | INPUT_CANCEL)) { 845 | removePointer = true; 846 | } 847 | 848 | // it not found, so the pointer hasn't been down (so it's probably a hover) 849 | if (storeIndex < 0) { 850 | return; 851 | } 852 | 853 | // update the event in the store 854 | store[storeIndex] = ev; 855 | 856 | this.callback(this.manager, eventType, { 857 | pointers: store, 858 | changedPointers: [ev], 859 | pointerType: pointerType, 860 | srcEvent: ev 861 | }); 862 | 863 | if (removePointer) { 864 | // remove from the store 865 | store.splice(storeIndex, 1); 866 | } 867 | } 868 | }); 869 | 870 | var SINGLE_TOUCH_INPUT_MAP = { 871 | touchstart: INPUT_START, 872 | touchmove: INPUT_MOVE, 873 | touchend: INPUT_END, 874 | touchcancel: INPUT_CANCEL 875 | }; 876 | 877 | var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart'; 878 | var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel'; 879 | 880 | /** 881 | * Touch events input 882 | * @constructor 883 | * @extends Input 884 | */ 885 | function SingleTouchInput() { 886 | this.evTarget = SINGLE_TOUCH_TARGET_EVENTS; 887 | this.evWin = SINGLE_TOUCH_WINDOW_EVENTS; 888 | this.started = false; 889 | 890 | Input.apply(this, arguments); 891 | } 892 | 893 | inherit(SingleTouchInput, Input, { 894 | handler: function TEhandler(ev) { 895 | var type = SINGLE_TOUCH_INPUT_MAP[ev.type]; 896 | 897 | // should we handle the touch events? 898 | if (type === INPUT_START) { 899 | this.started = true; 900 | } 901 | 902 | if (!this.started) { 903 | return; 904 | } 905 | 906 | var touches = normalizeSingleTouches.call(this, ev, type); 907 | 908 | // when done, reset the started state 909 | if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) { 910 | this.started = false; 911 | } 912 | 913 | this.callback(this.manager, type, { 914 | pointers: touches[0], 915 | changedPointers: touches[1], 916 | pointerType: INPUT_TYPE_TOUCH, 917 | srcEvent: ev 918 | }); 919 | } 920 | }); 921 | 922 | /** 923 | * @this {TouchInput} 924 | * @param {Object} ev 925 | * @param {Number} type flag 926 | * @returns {undefined|Array} [all, changed] 927 | */ 928 | function normalizeSingleTouches(ev, type) { 929 | var all = toArray(ev.touches); 930 | var changed = toArray(ev.changedTouches); 931 | 932 | if (type & (INPUT_END | INPUT_CANCEL)) { 933 | all = uniqueArray(all.concat(changed), 'identifier', true); 934 | } 935 | 936 | return [all, changed]; 937 | } 938 | 939 | var TOUCH_INPUT_MAP = { 940 | touchstart: INPUT_START, 941 | touchmove: INPUT_MOVE, 942 | touchend: INPUT_END, 943 | touchcancel: INPUT_CANCEL 944 | }; 945 | 946 | var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; 947 | 948 | /** 949 | * Multi-user touch events input 950 | * @constructor 951 | * @extends Input 952 | */ 953 | function TouchInput() { 954 | this.evTarget = TOUCH_TARGET_EVENTS; 955 | this.targetIds = {}; 956 | 957 | Input.apply(this, arguments); 958 | } 959 | 960 | inherit(TouchInput, Input, { 961 | handler: function MTEhandler(ev) { 962 | var type = TOUCH_INPUT_MAP[ev.type]; 963 | var touches = getTouches.call(this, ev, type); 964 | if (!touches) { 965 | return; 966 | } 967 | 968 | this.callback(this.manager, type, { 969 | pointers: touches[0], 970 | changedPointers: touches[1], 971 | pointerType: INPUT_TYPE_TOUCH, 972 | srcEvent: ev 973 | }); 974 | } 975 | }); 976 | 977 | /** 978 | * @this {TouchInput} 979 | * @param {Object} ev 980 | * @param {Number} type flag 981 | * @returns {undefined|Array} [all, changed] 982 | */ 983 | function getTouches(ev, type) { 984 | var allTouches = toArray(ev.touches); 985 | var targetIds = this.targetIds; 986 | 987 | // when there is only one touch, the process can be simplified 988 | if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) { 989 | targetIds[allTouches[0].identifier] = true; 990 | return [allTouches, allTouches]; 991 | } 992 | 993 | var i, 994 | targetTouches, 995 | changedTouches = toArray(ev.changedTouches), 996 | changedTargetTouches = [], 997 | target = this.target; 998 | 999 | // get target touches from touches 1000 | targetTouches = allTouches.filter(function(touch) { 1001 | return hasParent(touch.target, target); 1002 | }); 1003 | 1004 | // collect touches 1005 | if (type === INPUT_START) { 1006 | i = 0; 1007 | while (i < targetTouches.length) { 1008 | targetIds[targetTouches[i].identifier] = true; 1009 | i++; 1010 | } 1011 | } 1012 | 1013 | // filter changed touches to only contain touches that exist in the collected target ids 1014 | i = 0; 1015 | while (i < changedTouches.length) { 1016 | if (targetIds[changedTouches[i].identifier]) { 1017 | changedTargetTouches.push(changedTouches[i]); 1018 | } 1019 | 1020 | // cleanup removed touches 1021 | if (type & (INPUT_END | INPUT_CANCEL)) { 1022 | delete targetIds[changedTouches[i].identifier]; 1023 | } 1024 | i++; 1025 | } 1026 | 1027 | if (!changedTargetTouches.length) { 1028 | return; 1029 | } 1030 | 1031 | return [ 1032 | // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel' 1033 | uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true), 1034 | changedTargetTouches 1035 | ]; 1036 | } 1037 | 1038 | /** 1039 | * Combined touch and mouse input 1040 | * 1041 | * Touch has a higher priority then mouse, and while touching no mouse events are allowed. 1042 | * This because touch devices also emit mouse events while doing a touch. 1043 | * 1044 | * @constructor 1045 | * @extends Input 1046 | */ 1047 | function TouchMouseInput() { 1048 | Input.apply(this, arguments); 1049 | 1050 | var handler = bindFn(this.handler, this); 1051 | this.touch = new TouchInput(this.manager, handler); 1052 | this.mouse = new MouseInput(this.manager, handler); 1053 | } 1054 | 1055 | inherit(TouchMouseInput, Input, { 1056 | /** 1057 | * handle mouse and touch events 1058 | * @param {Hammer} manager 1059 | * @param {String} inputEvent 1060 | * @param {Object} inputData 1061 | */ 1062 | handler: function TMEhandler(manager, inputEvent, inputData) { 1063 | var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH), 1064 | isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE); 1065 | 1066 | // when we're in a touch event, so block all upcoming mouse events 1067 | // most mobile browser also emit mouseevents, right after touchstart 1068 | if (isTouch) { 1069 | this.mouse.allow = false; 1070 | } else if (isMouse && !this.mouse.allow) { 1071 | return; 1072 | } 1073 | 1074 | // reset the allowMouse when we're done 1075 | if (inputEvent & (INPUT_END | INPUT_CANCEL)) { 1076 | this.mouse.allow = true; 1077 | } 1078 | 1079 | this.callback(manager, inputEvent, inputData); 1080 | }, 1081 | 1082 | /** 1083 | * remove the event listeners 1084 | */ 1085 | destroy: function destroy() { 1086 | this.touch.destroy(); 1087 | this.mouse.destroy(); 1088 | } 1089 | }); 1090 | 1091 | var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); 1092 | var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined; 1093 | 1094 | // magical touchAction value 1095 | var TOUCH_ACTION_COMPUTE = 'compute'; 1096 | var TOUCH_ACTION_AUTO = 'auto'; 1097 | var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented 1098 | var TOUCH_ACTION_NONE = 'none'; 1099 | var TOUCH_ACTION_PAN_X = 'pan-x'; 1100 | var TOUCH_ACTION_PAN_Y = 'pan-y'; 1101 | 1102 | /** 1103 | * Touch Action 1104 | * sets the touchAction property or uses the js alternative 1105 | * @param {Manager} manager 1106 | * @param {String} value 1107 | * @constructor 1108 | */ 1109 | function TouchAction(manager, value) { 1110 | this.manager = manager; 1111 | this.set(value); 1112 | } 1113 | 1114 | TouchAction.prototype = { 1115 | /** 1116 | * set the touchAction value on the element or enable the polyfill 1117 | * @param {String} value 1118 | */ 1119 | set: function(value) { 1120 | // find out the touch-action by the event handlers 1121 | if (value == TOUCH_ACTION_COMPUTE) { 1122 | value = this.compute(); 1123 | } 1124 | 1125 | if (NATIVE_TOUCH_ACTION && this.manager.element.style) { 1126 | this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; 1127 | } 1128 | this.actions = value.toLowerCase().trim(); 1129 | }, 1130 | 1131 | /** 1132 | * just re-set the touchAction value 1133 | */ 1134 | update: function() { 1135 | this.set(this.manager.options.touchAction); 1136 | }, 1137 | 1138 | /** 1139 | * compute the value for the touchAction property based on the recognizer's settings 1140 | * @returns {String} value 1141 | */ 1142 | compute: function() { 1143 | var actions = []; 1144 | each(this.manager.recognizers, function(recognizer) { 1145 | if (boolOrFn(recognizer.options.enable, [recognizer])) { 1146 | actions = actions.concat(recognizer.getTouchAction()); 1147 | } 1148 | }); 1149 | return cleanTouchActions(actions.join(' ')); 1150 | }, 1151 | 1152 | /** 1153 | * this method is called on each input cycle and provides the preventing of the browser behavior 1154 | * @param {Object} input 1155 | */ 1156 | preventDefaults: function(input) { 1157 | // not needed with native support for the touchAction property 1158 | if (NATIVE_TOUCH_ACTION) { 1159 | return; 1160 | } 1161 | 1162 | var srcEvent = input.srcEvent; 1163 | var direction = input.offsetDirection; 1164 | 1165 | // if the touch action did prevented once this session 1166 | if (this.manager.session.prevented) { 1167 | srcEvent.preventDefault(); 1168 | return; 1169 | } 1170 | 1171 | var actions = this.actions; 1172 | var hasNone = inStr(actions, TOUCH_ACTION_NONE); 1173 | var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); 1174 | var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); 1175 | 1176 | if (hasNone) { 1177 | //do not prevent defaults if this is a tap gesture 1178 | 1179 | var isTapPointer = input.pointers.length === 1; 1180 | var isTapMovement = input.distance < 2; 1181 | var isTapTouchTime = input.deltaTime < 250; 1182 | 1183 | if (isTapPointer && isTapMovement && isTapTouchTime) { 1184 | return; 1185 | } 1186 | } 1187 | 1188 | if (hasNone || 1189 | (hasPanY && direction & DIRECTION_HORIZONTAL) || 1190 | (hasPanX && direction & DIRECTION_VERTICAL)) { 1191 | return this.preventSrc(srcEvent); 1192 | } 1193 | }, 1194 | 1195 | /** 1196 | * call preventDefault to prevent the browser's default behavior (scrolling in most cases) 1197 | * @param {Object} srcEvent 1198 | */ 1199 | preventSrc: function(srcEvent) { 1200 | this.manager.session.prevented = true; 1201 | srcEvent.preventDefault(); 1202 | } 1203 | }; 1204 | 1205 | /** 1206 | * when the touchActions are collected they are not a valid value, so we need to clean things up. * 1207 | * @param {String} actions 1208 | * @returns {*} 1209 | */ 1210 | function cleanTouchActions(actions) { 1211 | // none 1212 | if (inStr(actions, TOUCH_ACTION_NONE)) { 1213 | return TOUCH_ACTION_NONE; 1214 | } 1215 | 1216 | var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); 1217 | var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); 1218 | 1219 | // pan-x and pan-y can be combined 1220 | if (hasPanX && hasPanY) { 1221 | return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y; 1222 | } 1223 | 1224 | // pan-x OR pan-y 1225 | if (hasPanX || hasPanY) { 1226 | return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; 1227 | } 1228 | 1229 | // manipulation 1230 | if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { 1231 | return TOUCH_ACTION_MANIPULATION; 1232 | } 1233 | 1234 | return TOUCH_ACTION_AUTO; 1235 | } 1236 | 1237 | /** 1238 | * Recognizer flow explained; * 1239 | * All recognizers have the initial state of POSSIBLE when a input session starts. 1240 | * The definition of a input session is from the first input until the last input, with all it's movement in it. * 1241 | * Example session for mouse-input: mousedown -> mousemove -> mouseup 1242 | * 1243 | * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed 1244 | * which determines with state it should be. 1245 | * 1246 | * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to 1247 | * POSSIBLE to give it another change on the next cycle. 1248 | * 1249 | * Possible 1250 | * | 1251 | * +-----+---------------+ 1252 | * | | 1253 | * +-----+-----+ | 1254 | * | | | 1255 | * Failed Cancelled | 1256 | * +-------+------+ 1257 | * | | 1258 | * Recognized Began 1259 | * | 1260 | * Changed 1261 | * | 1262 | * Ended/Recognized 1263 | */ 1264 | var STATE_POSSIBLE = 1; 1265 | var STATE_BEGAN = 2; 1266 | var STATE_CHANGED = 4; 1267 | var STATE_ENDED = 8; 1268 | var STATE_RECOGNIZED = STATE_ENDED; 1269 | var STATE_CANCELLED = 16; 1270 | var STATE_FAILED = 32; 1271 | 1272 | /** 1273 | * Recognizer 1274 | * Every recognizer needs to extend from this class. 1275 | * @constructor 1276 | * @param {Object} options 1277 | */ 1278 | function Recognizer(options) { 1279 | // make sure, options are copied over to a new object to prevent leaking it outside 1280 | options = extend({}, options || {}); 1281 | 1282 | this.id = uniqueId(); 1283 | 1284 | this.manager = null; 1285 | this.options = merge(options, this.defaults); 1286 | 1287 | // default is enable true 1288 | this.options.enable = ifUndefined(this.options.enable, true); 1289 | 1290 | this.state = STATE_POSSIBLE; 1291 | 1292 | this.simultaneous = {}; 1293 | this.requireFail = []; 1294 | } 1295 | 1296 | Recognizer.prototype = { 1297 | /** 1298 | * @virtual 1299 | * @type {Object} 1300 | */ 1301 | defaults: {}, 1302 | 1303 | /** 1304 | * set options 1305 | * @param {Object} options 1306 | * @return {Recognizer} 1307 | */ 1308 | set: function(options) { 1309 | extend(this.options, options); 1310 | 1311 | // also update the touchAction, in case something changed about the directions/enabled state 1312 | this.manager && this.manager.touchAction.update(); 1313 | return this; 1314 | }, 1315 | 1316 | /** 1317 | * recognize simultaneous with an other recognizer. 1318 | * @param {Recognizer} otherRecognizer 1319 | * @returns {Recognizer} this 1320 | */ 1321 | recognizeWith: function(otherRecognizer) { 1322 | if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) { 1323 | return this; 1324 | } 1325 | 1326 | var simultaneous = this.simultaneous; 1327 | otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 1328 | if (!simultaneous[otherRecognizer.id]) { 1329 | simultaneous[otherRecognizer.id] = otherRecognizer; 1330 | otherRecognizer.recognizeWith(this); 1331 | } 1332 | return this; 1333 | }, 1334 | 1335 | /** 1336 | * drop the simultaneous link. it doesnt remove the link on the other recognizer. 1337 | * @param {Recognizer} otherRecognizer 1338 | * @returns {Recognizer} this 1339 | */ 1340 | dropRecognizeWith: function(otherRecognizer) { 1341 | if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) { 1342 | return this; 1343 | } 1344 | 1345 | otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 1346 | delete this.simultaneous[otherRecognizer.id]; 1347 | return this; 1348 | }, 1349 | 1350 | /** 1351 | * recognizer can only run when an other is failing 1352 | * @param {Recognizer} otherRecognizer 1353 | * @returns {Recognizer} this 1354 | */ 1355 | requireFailure: function(otherRecognizer) { 1356 | if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) { 1357 | return this; 1358 | } 1359 | 1360 | var requireFail = this.requireFail; 1361 | otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 1362 | if (inArray(requireFail, otherRecognizer) === -1) { 1363 | requireFail.push(otherRecognizer); 1364 | otherRecognizer.requireFailure(this); 1365 | } 1366 | return this; 1367 | }, 1368 | 1369 | /** 1370 | * drop the requireFailure link. it does not remove the link on the other recognizer. 1371 | * @param {Recognizer} otherRecognizer 1372 | * @returns {Recognizer} this 1373 | */ 1374 | dropRequireFailure: function(otherRecognizer) { 1375 | if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) { 1376 | return this; 1377 | } 1378 | 1379 | otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 1380 | var index = inArray(this.requireFail, otherRecognizer); 1381 | if (index > -1) { 1382 | this.requireFail.splice(index, 1); 1383 | } 1384 | return this; 1385 | }, 1386 | 1387 | /** 1388 | * has require failures boolean 1389 | * @returns {boolean} 1390 | */ 1391 | hasRequireFailures: function() { 1392 | return this.requireFail.length > 0; 1393 | }, 1394 | 1395 | /** 1396 | * if the recognizer can recognize simultaneous with an other recognizer 1397 | * @param {Recognizer} otherRecognizer 1398 | * @returns {Boolean} 1399 | */ 1400 | canRecognizeWith: function(otherRecognizer) { 1401 | return !!this.simultaneous[otherRecognizer.id]; 1402 | }, 1403 | 1404 | /** 1405 | * You should use `tryEmit` instead of `emit` directly to check 1406 | * that all the needed recognizers has failed before emitting. 1407 | * @param {Object} input 1408 | */ 1409 | emit: function(input) { 1410 | var self = this; 1411 | var state = this.state; 1412 | 1413 | function emit(event) { 1414 | self.manager.emit(event, input); 1415 | } 1416 | 1417 | // 'panstart' and 'panmove' 1418 | if (state < STATE_ENDED) { 1419 | emit(self.options.event + stateStr(state)); 1420 | } 1421 | 1422 | emit(self.options.event); // simple 'eventName' events 1423 | 1424 | if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...) 1425 | emit(input.additionalEvent); 1426 | } 1427 | 1428 | // panend and pancancel 1429 | if (state >= STATE_ENDED) { 1430 | emit(self.options.event + stateStr(state)); 1431 | } 1432 | }, 1433 | 1434 | /** 1435 | * Check that all the require failure recognizers has failed, 1436 | * if true, it emits a gesture event, 1437 | * otherwise, setup the state to FAILED. 1438 | * @param {Object} input 1439 | */ 1440 | tryEmit: function(input) { 1441 | if (this.canEmit()) { 1442 | return this.emit(input); 1443 | } 1444 | // it's failing anyway 1445 | this.state = STATE_FAILED; 1446 | }, 1447 | 1448 | /** 1449 | * can we emit? 1450 | * @returns {boolean} 1451 | */ 1452 | canEmit: function() { 1453 | var i = 0; 1454 | while (i < this.requireFail.length) { 1455 | if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) { 1456 | return false; 1457 | } 1458 | i++; 1459 | } 1460 | return true; 1461 | }, 1462 | 1463 | /** 1464 | * update the recognizer 1465 | * @param {Object} inputData 1466 | */ 1467 | recognize: function(inputData) { 1468 | // make a new copy of the inputData 1469 | // so we can change the inputData without messing up the other recognizers 1470 | var inputDataClone = extend({}, inputData); 1471 | 1472 | // is is enabled and allow recognizing? 1473 | if (!boolOrFn(this.options.enable, [this, inputDataClone])) { 1474 | this.reset(); 1475 | this.state = STATE_FAILED; 1476 | return; 1477 | } 1478 | 1479 | // reset when we've reached the end 1480 | if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) { 1481 | this.state = STATE_POSSIBLE; 1482 | } 1483 | 1484 | this.state = this.process(inputDataClone); 1485 | 1486 | // the recognizer has recognized a gesture 1487 | // so trigger an event 1488 | if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) { 1489 | this.tryEmit(inputDataClone); 1490 | } 1491 | }, 1492 | 1493 | /** 1494 | * return the state of the recognizer 1495 | * the actual recognizing happens in this method 1496 | * @virtual 1497 | * @param {Object} inputData 1498 | * @returns {Const} STATE 1499 | */ 1500 | process: function(inputData) { }, // jshint ignore:line 1501 | 1502 | /** 1503 | * return the preferred touch-action 1504 | * @virtual 1505 | * @returns {Array} 1506 | */ 1507 | getTouchAction: function() { }, 1508 | 1509 | /** 1510 | * called when the gesture isn't allowed to recognize 1511 | * like when another is being recognized or it is disabled 1512 | * @virtual 1513 | */ 1514 | reset: function() { } 1515 | }; 1516 | 1517 | /** 1518 | * get a usable string, used as event postfix 1519 | * @param {Const} state 1520 | * @returns {String} state 1521 | */ 1522 | function stateStr(state) { 1523 | if (state & STATE_CANCELLED) { 1524 | return 'cancel'; 1525 | } else if (state & STATE_ENDED) { 1526 | return 'end'; 1527 | } else if (state & STATE_CHANGED) { 1528 | return 'move'; 1529 | } else if (state & STATE_BEGAN) { 1530 | return 'start'; 1531 | } 1532 | return ''; 1533 | } 1534 | 1535 | /** 1536 | * direction cons to string 1537 | * @param {Const} direction 1538 | * @returns {String} 1539 | */ 1540 | function directionStr(direction) { 1541 | if (direction == DIRECTION_DOWN) { 1542 | return 'down'; 1543 | } else if (direction == DIRECTION_UP) { 1544 | return 'up'; 1545 | } else if (direction == DIRECTION_LEFT) { 1546 | return 'left'; 1547 | } else if (direction == DIRECTION_RIGHT) { 1548 | return 'right'; 1549 | } 1550 | return ''; 1551 | } 1552 | 1553 | /** 1554 | * get a recognizer by name if it is bound to a manager 1555 | * @param {Recognizer|String} otherRecognizer 1556 | * @param {Recognizer} recognizer 1557 | * @returns {Recognizer} 1558 | */ 1559 | function getRecognizerByNameIfManager(otherRecognizer, recognizer) { 1560 | var manager = recognizer.manager; 1561 | if (manager) { 1562 | return manager.get(otherRecognizer); 1563 | } 1564 | return otherRecognizer; 1565 | } 1566 | 1567 | /** 1568 | * This recognizer is just used as a base for the simple attribute recognizers. 1569 | * @constructor 1570 | * @extends Recognizer 1571 | */ 1572 | function AttrRecognizer() { 1573 | Recognizer.apply(this, arguments); 1574 | } 1575 | 1576 | inherit(AttrRecognizer, Recognizer, { 1577 | /** 1578 | * @namespace 1579 | * @memberof AttrRecognizer 1580 | */ 1581 | defaults: { 1582 | /** 1583 | * @type {Number} 1584 | * @default 1 1585 | */ 1586 | pointers: 1 1587 | }, 1588 | 1589 | /** 1590 | * Used to check if it the recognizer receives valid input, like input.distance > 10. 1591 | * @memberof AttrRecognizer 1592 | * @param {Object} input 1593 | * @returns {Boolean} recognized 1594 | */ 1595 | attrTest: function(input) { 1596 | var optionPointers = this.options.pointers; 1597 | return optionPointers === 0 || input.pointers.length === optionPointers; 1598 | }, 1599 | 1600 | /** 1601 | * Process the input and return the state for the recognizer 1602 | * @memberof AttrRecognizer 1603 | * @param {Object} input 1604 | * @returns {*} State 1605 | */ 1606 | process: function(input) { 1607 | var state = this.state; 1608 | var eventType = input.eventType; 1609 | 1610 | var isRecognized = state & (STATE_BEGAN | STATE_CHANGED); 1611 | var isValid = this.attrTest(input); 1612 | 1613 | // on cancel input and we've recognized before, return STATE_CANCELLED 1614 | if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) { 1615 | return state | STATE_CANCELLED; 1616 | } else if (isRecognized || isValid) { 1617 | if (eventType & INPUT_END) { 1618 | return state | STATE_ENDED; 1619 | } else if (!(state & STATE_BEGAN)) { 1620 | return STATE_BEGAN; 1621 | } 1622 | return state | STATE_CHANGED; 1623 | } 1624 | return STATE_FAILED; 1625 | } 1626 | }); 1627 | 1628 | /** 1629 | * Pan 1630 | * Recognized when the pointer is down and moved in the allowed direction. 1631 | * @constructor 1632 | * @extends AttrRecognizer 1633 | */ 1634 | function PanRecognizer() { 1635 | AttrRecognizer.apply(this, arguments); 1636 | 1637 | this.pX = null; 1638 | this.pY = null; 1639 | } 1640 | 1641 | inherit(PanRecognizer, AttrRecognizer, { 1642 | /** 1643 | * @namespace 1644 | * @memberof PanRecognizer 1645 | */ 1646 | defaults: { 1647 | event: 'pan', 1648 | threshold: 10, 1649 | pointers: 1, 1650 | direction: DIRECTION_ALL 1651 | }, 1652 | 1653 | getTouchAction: function() { 1654 | var direction = this.options.direction; 1655 | var actions = []; 1656 | if (direction & DIRECTION_HORIZONTAL) { 1657 | actions.push(TOUCH_ACTION_PAN_X); 1658 | } 1659 | if (direction & DIRECTION_VERTICAL) { 1660 | actions.push(TOUCH_ACTION_PAN_Y); 1661 | } 1662 | return actions; 1663 | }, 1664 | 1665 | directionTest: function(input) { 1666 | var options = this.options; 1667 | var hasMoved = true; 1668 | var distance = input.distance; 1669 | var direction = input.direction; 1670 | var x = input.deltaX; 1671 | var y = input.deltaY; 1672 | 1673 | // lock to axis? 1674 | if (!(direction & options.direction)) { 1675 | if (options.direction & DIRECTION_HORIZONTAL) { 1676 | direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; 1677 | hasMoved = x != this.pX; 1678 | distance = Math.abs(input.deltaX); 1679 | } else { 1680 | direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN; 1681 | hasMoved = y != this.pY; 1682 | distance = Math.abs(input.deltaY); 1683 | } 1684 | } 1685 | input.direction = direction; 1686 | return hasMoved && distance > options.threshold && direction & options.direction; 1687 | }, 1688 | 1689 | attrTest: function(input) { 1690 | return AttrRecognizer.prototype.attrTest.call(this, input) && 1691 | (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input))); 1692 | }, 1693 | 1694 | emit: function(input) { 1695 | 1696 | this.pX = input.deltaX; 1697 | this.pY = input.deltaY; 1698 | 1699 | var direction = directionStr(input.direction); 1700 | 1701 | if (direction) { 1702 | input.additionalEvent = this.options.event + direction; 1703 | } 1704 | this._super.emit.call(this, input); 1705 | } 1706 | }); 1707 | 1708 | /** 1709 | * Pinch 1710 | * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out). 1711 | * @constructor 1712 | * @extends AttrRecognizer 1713 | */ 1714 | function PinchRecognizer() { 1715 | AttrRecognizer.apply(this, arguments); 1716 | } 1717 | 1718 | inherit(PinchRecognizer, AttrRecognizer, { 1719 | /** 1720 | * @namespace 1721 | * @memberof PinchRecognizer 1722 | */ 1723 | defaults: { 1724 | event: 'pinch', 1725 | threshold: 0, 1726 | pointers: 2 1727 | }, 1728 | 1729 | getTouchAction: function() { 1730 | return [TOUCH_ACTION_NONE]; 1731 | }, 1732 | 1733 | attrTest: function(input) { 1734 | return this._super.attrTest.call(this, input) && 1735 | (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN); 1736 | }, 1737 | 1738 | emit: function(input) { 1739 | if (input.scale !== 1) { 1740 | var inOut = input.scale < 1 ? 'in' : 'out'; 1741 | input.additionalEvent = this.options.event + inOut; 1742 | } 1743 | this._super.emit.call(this, input); 1744 | } 1745 | }); 1746 | 1747 | /** 1748 | * Press 1749 | * Recognized when the pointer is down for x ms without any movement. 1750 | * @constructor 1751 | * @extends Recognizer 1752 | */ 1753 | function PressRecognizer() { 1754 | Recognizer.apply(this, arguments); 1755 | 1756 | this._timer = null; 1757 | this._input = null; 1758 | } 1759 | 1760 | inherit(PressRecognizer, Recognizer, { 1761 | /** 1762 | * @namespace 1763 | * @memberof PressRecognizer 1764 | */ 1765 | defaults: { 1766 | event: 'press', 1767 | pointers: 1, 1768 | time: 500, // minimal time of the pointer to be pressed 1769 | threshold: 5 // a minimal movement is ok, but keep it low 1770 | }, 1771 | 1772 | getTouchAction: function() { 1773 | return [TOUCH_ACTION_AUTO]; 1774 | }, 1775 | 1776 | process: function(input) { 1777 | var options = this.options; 1778 | var validPointers = input.pointers.length === options.pointers; 1779 | var validMovement = input.distance < options.threshold; 1780 | var validTime = input.deltaTime > options.time; 1781 | 1782 | this._input = input; 1783 | 1784 | // we only allow little movement 1785 | // and we've reached an end event, so a tap is possible 1786 | if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) { 1787 | this.reset(); 1788 | } else if (input.eventType & INPUT_START) { 1789 | this.reset(); 1790 | this._timer = setTimeoutContext(function() { 1791 | this.state = STATE_RECOGNIZED; 1792 | this.tryEmit(); 1793 | }, options.time, this); 1794 | } else if (input.eventType & INPUT_END) { 1795 | return STATE_RECOGNIZED; 1796 | } 1797 | return STATE_FAILED; 1798 | }, 1799 | 1800 | reset: function() { 1801 | clearTimeout(this._timer); 1802 | }, 1803 | 1804 | emit: function(input) { 1805 | if (this.state !== STATE_RECOGNIZED) { 1806 | return; 1807 | } 1808 | 1809 | if (input && (input.eventType & INPUT_END)) { 1810 | this.manager.emit(this.options.event + 'up', input); 1811 | } else { 1812 | this._input.timeStamp = now(); 1813 | this.manager.emit(this.options.event, this._input); 1814 | } 1815 | } 1816 | }); 1817 | 1818 | /** 1819 | * Rotate 1820 | * Recognized when two or more pointer are moving in a circular motion. 1821 | * @constructor 1822 | * @extends AttrRecognizer 1823 | */ 1824 | function RotateRecognizer() { 1825 | AttrRecognizer.apply(this, arguments); 1826 | } 1827 | 1828 | inherit(RotateRecognizer, AttrRecognizer, { 1829 | /** 1830 | * @namespace 1831 | * @memberof RotateRecognizer 1832 | */ 1833 | defaults: { 1834 | event: 'rotate', 1835 | threshold: 0, 1836 | pointers: 2 1837 | }, 1838 | 1839 | getTouchAction: function() { 1840 | return [TOUCH_ACTION_NONE]; 1841 | }, 1842 | 1843 | attrTest: function(input) { 1844 | return this._super.attrTest.call(this, input) && 1845 | (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN); 1846 | } 1847 | }); 1848 | 1849 | /** 1850 | * Swipe 1851 | * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction. 1852 | * @constructor 1853 | * @extends AttrRecognizer 1854 | */ 1855 | function SwipeRecognizer() { 1856 | AttrRecognizer.apply(this, arguments); 1857 | } 1858 | 1859 | inherit(SwipeRecognizer, AttrRecognizer, { 1860 | /** 1861 | * @namespace 1862 | * @memberof SwipeRecognizer 1863 | */ 1864 | defaults: { 1865 | event: 'swipe', 1866 | threshold: 10, 1867 | velocity: 0.65, 1868 | direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL, 1869 | pointers: 1 1870 | }, 1871 | 1872 | getTouchAction: function() { 1873 | return PanRecognizer.prototype.getTouchAction.call(this); 1874 | }, 1875 | 1876 | attrTest: function(input) { 1877 | var direction = this.options.direction; 1878 | var velocity; 1879 | 1880 | if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) { 1881 | velocity = input.overallVelocity; 1882 | } else if (direction & DIRECTION_HORIZONTAL) { 1883 | velocity = input.overallVelocityX; 1884 | } else if (direction & DIRECTION_VERTICAL) { 1885 | velocity = input.overallVelocityY; 1886 | } 1887 | 1888 | return this._super.attrTest.call(this, input) && 1889 | direction & input.offsetDirection && 1890 | input.distance > this.options.threshold && 1891 | input.maxPointers == this.options.pointers && 1892 | abs(velocity) > this.options.velocity && input.eventType & INPUT_END; 1893 | }, 1894 | 1895 | emit: function(input) { 1896 | var direction = directionStr(input.offsetDirection); 1897 | if (direction) { 1898 | this.manager.emit(this.options.event + direction, input); 1899 | } 1900 | 1901 | this.manager.emit(this.options.event, input); 1902 | } 1903 | }); 1904 | 1905 | /** 1906 | * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur 1907 | * between the given interval and position. The delay option can be used to recognize multi-taps without firing 1908 | * a single tap. 1909 | * 1910 | * The eventData from the emitted event contains the property `tapCount`, which contains the amount of 1911 | * multi-taps being recognized. 1912 | * @constructor 1913 | * @extends Recognizer 1914 | */ 1915 | function TapRecognizer() { 1916 | Recognizer.apply(this, arguments); 1917 | 1918 | // previous time and center, 1919 | // used for tap counting 1920 | this.pTime = false; 1921 | this.pCenter = false; 1922 | 1923 | this._timer = null; 1924 | this._input = null; 1925 | this.count = 0; 1926 | } 1927 | 1928 | inherit(TapRecognizer, Recognizer, { 1929 | /** 1930 | * @namespace 1931 | * @memberof PinchRecognizer 1932 | */ 1933 | defaults: { 1934 | event: 'tap', 1935 | pointers: 1, 1936 | taps: 1, 1937 | interval: 300, // max time between the multi-tap taps 1938 | time: 250, // max time of the pointer to be down (like finger on the screen) 1939 | threshold: 2, // a minimal movement is ok, but keep it low 1940 | posThreshold: 10 // a multi-tap can be a bit off the initial position 1941 | }, 1942 | 1943 | getTouchAction: function() { 1944 | return [TOUCH_ACTION_MANIPULATION]; 1945 | }, 1946 | 1947 | process: function(input) { 1948 | var options = this.options; 1949 | 1950 | var validPointers = input.pointers.length === options.pointers; 1951 | var validMovement = input.distance < options.threshold; 1952 | var validTouchTime = input.deltaTime < options.time; 1953 | 1954 | this.reset(); 1955 | 1956 | if ((input.eventType & INPUT_START) && (this.count === 0)) { 1957 | return this.failTimeout(); 1958 | } 1959 | 1960 | // we only allow little movement 1961 | // and we've reached an end event, so a tap is possible 1962 | if (validMovement && validTouchTime && validPointers) { 1963 | if (input.eventType != INPUT_END) { 1964 | return this.failTimeout(); 1965 | } 1966 | 1967 | var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true; 1968 | var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold; 1969 | 1970 | this.pTime = input.timeStamp; 1971 | this.pCenter = input.center; 1972 | 1973 | if (!validMultiTap || !validInterval) { 1974 | this.count = 1; 1975 | } else { 1976 | this.count += 1; 1977 | } 1978 | 1979 | this._input = input; 1980 | 1981 | // if tap count matches we have recognized it, 1982 | // else it has began recognizing... 1983 | var tapCount = this.count % options.taps; 1984 | if (tapCount === 0) { 1985 | // no failing requirements, immediately trigger the tap event 1986 | // or wait as long as the multitap interval to trigger 1987 | if (!this.hasRequireFailures()) { 1988 | return STATE_RECOGNIZED; 1989 | } else { 1990 | this._timer = setTimeoutContext(function() { 1991 | this.state = STATE_RECOGNIZED; 1992 | this.tryEmit(); 1993 | }, options.interval, this); 1994 | return STATE_BEGAN; 1995 | } 1996 | } 1997 | } 1998 | return STATE_FAILED; 1999 | }, 2000 | 2001 | failTimeout: function() { 2002 | this._timer = setTimeoutContext(function() { 2003 | this.state = STATE_FAILED; 2004 | }, this.options.interval, this); 2005 | return STATE_FAILED; 2006 | }, 2007 | 2008 | reset: function() { 2009 | clearTimeout(this._timer); 2010 | }, 2011 | 2012 | emit: function() { 2013 | if (this.state == STATE_RECOGNIZED) { 2014 | this._input.tapCount = this.count; 2015 | this.manager.emit(this.options.event, this._input); 2016 | } 2017 | } 2018 | }); 2019 | 2020 | /** 2021 | * Simple way to create an manager with a default set of recognizers. 2022 | * @param {HTMLElement} element 2023 | * @param {Object} [options] 2024 | * @constructor 2025 | */ 2026 | function Hammer(element, options) { 2027 | options = options || {}; 2028 | options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset); 2029 | return new Manager(element, options); 2030 | } 2031 | 2032 | /** 2033 | * @const {string} 2034 | */ 2035 | Hammer.VERSION = '2.0.4'; 2036 | 2037 | /** 2038 | * default settings 2039 | * @namespace 2040 | */ 2041 | Hammer.defaults = { 2042 | /** 2043 | * set if DOM events are being triggered. 2044 | * But this is slower and unused by simple implementations, so disabled by default. 2045 | * @type {Boolean} 2046 | * @default false 2047 | */ 2048 | domEvents: false, 2049 | 2050 | /** 2051 | * The value for the touchAction property/fallback. 2052 | * When set to `compute` it will magically set the correct value based on the added recognizers. 2053 | * @type {String} 2054 | * @default compute 2055 | */ 2056 | touchAction: TOUCH_ACTION_COMPUTE, 2057 | 2058 | /** 2059 | * @type {Boolean} 2060 | * @default true 2061 | */ 2062 | enable: true, 2063 | 2064 | /** 2065 | * EXPERIMENTAL FEATURE -- can be removed/changed 2066 | * Change the parent input target element. 2067 | * If Null, then it is being set the to main element. 2068 | * @type {Null|EventTarget} 2069 | * @default null 2070 | */ 2071 | inputTarget: null, 2072 | 2073 | /** 2074 | * force an input class 2075 | * @type {Null|Function} 2076 | * @default null 2077 | */ 2078 | inputClass: null, 2079 | 2080 | /** 2081 | * Default recognizer setup when calling `Hammer()` 2082 | * When creating a new Manager these will be skipped. 2083 | * @type {Array} 2084 | */ 2085 | preset: [ 2086 | // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...] 2087 | [RotateRecognizer, {enable: false}], 2088 | [PinchRecognizer, {enable: false}, ['rotate']], 2089 | [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}], 2090 | [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']], 2091 | [TapRecognizer], 2092 | [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']], 2093 | [PressRecognizer] 2094 | ], 2095 | 2096 | /** 2097 | * Some CSS properties can be used to improve the working of Hammer. 2098 | * Add them to this method and they will be set when creating a new Manager. 2099 | * @namespace 2100 | */ 2101 | cssProps: { 2102 | /** 2103 | * Disables text selection to improve the dragging gesture. Mainly for desktop browsers. 2104 | * @type {String} 2105 | * @default 'none' 2106 | */ 2107 | userSelect: 'none', 2108 | 2109 | /** 2110 | * Disable the Windows Phone grippers when pressing an element. 2111 | * @type {String} 2112 | * @default 'none' 2113 | */ 2114 | touchSelect: 'none', 2115 | 2116 | /** 2117 | * Disables the default callout shown when you touch and hold a touch target. 2118 | * On iOS, when you touch and hold a touch target such as a link, Safari displays 2119 | * a callout containing information about the link. This property allows you to disable that callout. 2120 | * @type {String} 2121 | * @default 'none' 2122 | */ 2123 | touchCallout: 'none', 2124 | 2125 | /** 2126 | * Specifies whether zooming is enabled. Used by IE10> 2127 | * @type {String} 2128 | * @default 'none' 2129 | */ 2130 | contentZooming: 'none', 2131 | 2132 | /** 2133 | * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers. 2134 | * @type {String} 2135 | * @default 'none' 2136 | */ 2137 | userDrag: 'none', 2138 | 2139 | /** 2140 | * Overrides the highlight color shown when the user taps a link or a JavaScript 2141 | * clickable element in iOS. This property obeys the alpha value, if specified. 2142 | * @type {String} 2143 | * @default 'rgba(0,0,0,0)' 2144 | */ 2145 | tapHighlightColor: 'rgba(0,0,0,0)' 2146 | } 2147 | }; 2148 | 2149 | var STOP = 1; 2150 | var FORCED_STOP = 2; 2151 | 2152 | /** 2153 | * Manager 2154 | * @param {HTMLElement} element 2155 | * @param {Object} [options] 2156 | * @constructor 2157 | */ 2158 | function Manager(element, options) { 2159 | options = options || {}; 2160 | 2161 | this.options = merge(options, Hammer.defaults); 2162 | this.options.inputTarget = this.options.inputTarget || element; 2163 | 2164 | this.handlers = {}; 2165 | this.session = {}; 2166 | this.recognizers = []; 2167 | 2168 | this.element = element; 2169 | this.input = createInputInstance(this); 2170 | this.touchAction = new TouchAction(this, this.options.touchAction); 2171 | 2172 | toggleCssProps(this, true); 2173 | 2174 | each(options.recognizers, function(item) { 2175 | var recognizer = this.add(new (item[0])(item[1])); 2176 | item[2] && recognizer.recognizeWith(item[2]); 2177 | item[3] && recognizer.requireFailure(item[3]); 2178 | }, this); 2179 | } 2180 | 2181 | Manager.prototype = { 2182 | /** 2183 | * set options 2184 | * @param {Object} options 2185 | * @returns {Manager} 2186 | */ 2187 | set: function(options) { 2188 | extend(this.options, options); 2189 | 2190 | // Options that need a little more setup 2191 | if (options.touchAction) { 2192 | this.touchAction.update(); 2193 | } 2194 | if (options.inputTarget) { 2195 | // Clean up existing event listeners and reinitialize 2196 | this.input.destroy(); 2197 | this.input.target = options.inputTarget; 2198 | this.input.init(); 2199 | } 2200 | return this; 2201 | }, 2202 | 2203 | /** 2204 | * stop recognizing for this session. 2205 | * This session will be discarded, when a new [input]start event is fired. 2206 | * When forced, the recognizer cycle is stopped immediately. 2207 | * @param {Boolean} [force] 2208 | */ 2209 | stop: function(force) { 2210 | this.session.stopped = force ? FORCED_STOP : STOP; 2211 | }, 2212 | 2213 | /** 2214 | * run the recognizers! 2215 | * called by the inputHandler function on every movement of the pointers (touches) 2216 | * it walks through all the recognizers and tries to detect the gesture that is being made 2217 | * @param {Object} inputData 2218 | */ 2219 | recognize: function(inputData) { 2220 | var session = this.session; 2221 | if (session.stopped) { 2222 | return; 2223 | } 2224 | 2225 | // run the touch-action polyfill 2226 | this.touchAction.preventDefaults(inputData); 2227 | 2228 | var recognizer; 2229 | var recognizers = this.recognizers; 2230 | 2231 | // this holds the recognizer that is being recognized. 2232 | // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED 2233 | // if no recognizer is detecting a thing, it is set to `null` 2234 | var curRecognizer = session.curRecognizer; 2235 | 2236 | // reset when the last recognizer is recognized 2237 | // or when we're in a new session 2238 | if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) { 2239 | curRecognizer = session.curRecognizer = null; 2240 | } 2241 | 2242 | var i = 0; 2243 | while (i < recognizers.length) { 2244 | recognizer = recognizers[i]; 2245 | 2246 | // find out if we are allowed try to recognize the input for this one. 2247 | // 1. allow if the session is NOT forced stopped (see the .stop() method) 2248 | // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one 2249 | // that is being recognized. 2250 | // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer. 2251 | // this can be setup with the `recognizeWith()` method on the recognizer. 2252 | if (session.stopped !== FORCED_STOP && ( // 1 2253 | !curRecognizer || recognizer == curRecognizer || // 2 2254 | recognizer.canRecognizeWith(curRecognizer))) { // 3 2255 | recognizer.recognize(inputData); 2256 | } else { 2257 | recognizer.reset(); 2258 | } 2259 | 2260 | // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the 2261 | // current active recognizer. but only if we don't already have an active recognizer 2262 | if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) { 2263 | curRecognizer = session.curRecognizer = recognizer; 2264 | } 2265 | i++; 2266 | } 2267 | }, 2268 | 2269 | /** 2270 | * get a recognizer by its event name. 2271 | * @param {Recognizer|String} recognizer 2272 | * @returns {Recognizer|Null} 2273 | */ 2274 | get: function(recognizer) { 2275 | if (recognizer instanceof Recognizer) { 2276 | return recognizer; 2277 | } 2278 | 2279 | var recognizers = this.recognizers; 2280 | for (var i = 0; i < recognizers.length; i++) { 2281 | if (recognizers[i].options.event == recognizer) { 2282 | return recognizers[i]; 2283 | } 2284 | } 2285 | return null; 2286 | }, 2287 | 2288 | /** 2289 | * add a recognizer to the manager 2290 | * existing recognizers with the same event name will be removed 2291 | * @param {Recognizer} recognizer 2292 | * @returns {Recognizer|Manager} 2293 | */ 2294 | add: function(recognizer) { 2295 | if (invokeArrayArg(recognizer, 'add', this)) { 2296 | return this; 2297 | } 2298 | 2299 | // remove existing 2300 | var existing = this.get(recognizer.options.event); 2301 | if (existing) { 2302 | this.remove(existing); 2303 | } 2304 | 2305 | this.recognizers.push(recognizer); 2306 | recognizer.manager = this; 2307 | 2308 | this.touchAction.update(); 2309 | return recognizer; 2310 | }, 2311 | 2312 | /** 2313 | * remove a recognizer by name or instance 2314 | * @param {Recognizer|String} recognizer 2315 | * @returns {Manager} 2316 | */ 2317 | remove: function(recognizer) { 2318 | if (invokeArrayArg(recognizer, 'remove', this)) { 2319 | return this; 2320 | } 2321 | 2322 | var recognizers = this.recognizers; 2323 | recognizer = this.get(recognizer); 2324 | recognizers.splice(inArray(recognizers, recognizer), 1); 2325 | 2326 | this.touchAction.update(); 2327 | return this; 2328 | }, 2329 | 2330 | /** 2331 | * bind event 2332 | * @param {String} events 2333 | * @param {Function} handler 2334 | * @returns {EventEmitter} this 2335 | */ 2336 | on: function(events, handler) { 2337 | var handlers = this.handlers; 2338 | each(splitStr(events), function(event) { 2339 | handlers[event] = handlers[event] || []; 2340 | handlers[event].push(handler); 2341 | }); 2342 | return this; 2343 | }, 2344 | 2345 | /** 2346 | * unbind event, leave emit blank to remove all handlers 2347 | * @param {String} events 2348 | * @param {Function} [handler] 2349 | * @returns {EventEmitter} this 2350 | */ 2351 | off: function(events, handler) { 2352 | var handlers = this.handlers; 2353 | each(splitStr(events), function(event) { 2354 | if (!handler) { 2355 | delete handlers[event]; 2356 | } else { 2357 | handlers[event].splice(inArray(handlers[event], handler), 1); 2358 | } 2359 | }); 2360 | return this; 2361 | }, 2362 | 2363 | /** 2364 | * emit event to the listeners 2365 | * @param {String} event 2366 | * @param {Object} data 2367 | */ 2368 | emit: function(event, data) { 2369 | // we also want to trigger dom events 2370 | if (this.options.domEvents) { 2371 | triggerDomEvent(event, data); 2372 | } 2373 | 2374 | // no handlers, so skip it all 2375 | var handlers = this.handlers[event] && this.handlers[event].slice(); 2376 | if (!handlers || !handlers.length) { 2377 | return; 2378 | } 2379 | 2380 | data.type = event; 2381 | data.preventDefault = function() { 2382 | data.srcEvent.preventDefault(); 2383 | }; 2384 | 2385 | var i = 0; 2386 | while (i < handlers.length) { 2387 | handlers[i](data); 2388 | i++; 2389 | } 2390 | }, 2391 | 2392 | /** 2393 | * destroy the manager and unbinds all events 2394 | * it doesn't unbind dom events, that is the user own responsibility 2395 | */ 2396 | destroy: function() { 2397 | this.element && toggleCssProps(this, false); 2398 | 2399 | this.handlers = {}; 2400 | this.session = {}; 2401 | this.input.destroy(); 2402 | this.element = null; 2403 | } 2404 | }; 2405 | 2406 | /** 2407 | * add/remove the css properties as defined in manager.options.cssProps 2408 | * @param {Manager} manager 2409 | * @param {Boolean} add 2410 | */ 2411 | function toggleCssProps(manager, add) { 2412 | var element = manager.element; 2413 | if (!element.style) { 2414 | return; 2415 | } 2416 | each(manager.options.cssProps, function(value, name) { 2417 | element.style[prefixed(element.style, name)] = add ? value : ''; 2418 | }); 2419 | } 2420 | 2421 | /** 2422 | * trigger dom event 2423 | * @param {String} event 2424 | * @param {Object} data 2425 | */ 2426 | function triggerDomEvent(event, data) { 2427 | var gestureEvent = document.createEvent('Event'); 2428 | gestureEvent.initEvent(event, true, true); 2429 | gestureEvent.gesture = data; 2430 | data.target.dispatchEvent(gestureEvent); 2431 | } 2432 | 2433 | extend(Hammer, { 2434 | INPUT_START: INPUT_START, 2435 | INPUT_MOVE: INPUT_MOVE, 2436 | INPUT_END: INPUT_END, 2437 | INPUT_CANCEL: INPUT_CANCEL, 2438 | 2439 | STATE_POSSIBLE: STATE_POSSIBLE, 2440 | STATE_BEGAN: STATE_BEGAN, 2441 | STATE_CHANGED: STATE_CHANGED, 2442 | STATE_ENDED: STATE_ENDED, 2443 | STATE_RECOGNIZED: STATE_RECOGNIZED, 2444 | STATE_CANCELLED: STATE_CANCELLED, 2445 | STATE_FAILED: STATE_FAILED, 2446 | 2447 | DIRECTION_NONE: DIRECTION_NONE, 2448 | DIRECTION_LEFT: DIRECTION_LEFT, 2449 | DIRECTION_RIGHT: DIRECTION_RIGHT, 2450 | DIRECTION_UP: DIRECTION_UP, 2451 | DIRECTION_DOWN: DIRECTION_DOWN, 2452 | DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL, 2453 | DIRECTION_VERTICAL: DIRECTION_VERTICAL, 2454 | DIRECTION_ALL: DIRECTION_ALL, 2455 | 2456 | Manager: Manager, 2457 | Input: Input, 2458 | TouchAction: TouchAction, 2459 | 2460 | TouchInput: TouchInput, 2461 | MouseInput: MouseInput, 2462 | PointerEventInput: PointerEventInput, 2463 | TouchMouseInput: TouchMouseInput, 2464 | SingleTouchInput: SingleTouchInput, 2465 | 2466 | Recognizer: Recognizer, 2467 | AttrRecognizer: AttrRecognizer, 2468 | Tap: TapRecognizer, 2469 | Pan: PanRecognizer, 2470 | Swipe: SwipeRecognizer, 2471 | Pinch: PinchRecognizer, 2472 | Rotate: RotateRecognizer, 2473 | Press: PressRecognizer, 2474 | 2475 | on: addEventListeners, 2476 | off: removeEventListeners, 2477 | each: each, 2478 | merge: merge, 2479 | extend: extend, 2480 | inherit: inherit, 2481 | bindFn: bindFn, 2482 | prefixed: prefixed 2483 | }); 2484 | 2485 | if (typeof define == TYPE_FUNCTION && define.amd) { 2486 | define(function() { 2487 | return Hammer; 2488 | }); 2489 | } else if (typeof module != 'undefined' && module.exports) { 2490 | module.exports = Hammer; 2491 | } else { 2492 | window[exportName] = Hammer; 2493 | } 2494 | 2495 | })(window, document, 'Hammer'); 2496 | --------------------------------------------------------------------------------