├── .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 |
--------------------------------------------------------------------------------