Input handling library for Zdog.
1/** Zfetch
2 * Input handler for Zdog
3 */
4
5( function( root, factory ) {
6 // module definition
7 if ( typeof module == 'object' && module.exports ) {
8 // CommonJS
9 module.exports = factory();
10 } else {
11 // browser global
12 root.Zfetch = factory();
13 }
14}( this, function factory() {
15var DEBUG = false;
16if ( typeof window == 'undefined' || !window.PointerEvent ) {
17 console.error( 'Zfetch requires PointerEvent support.' );
18 return;
19}
20
21function noop() {}
22
23Zfetch.prototype.rotateStart = function( pointer ) {
24 this.rotateStartX = this.scene.rotate.x;
25 this.rotateStartY = this.scene.rotate.y;
26}
27
28Zfetch.prototype.rotateMove = function( pointer, target, moveX, moveY ) {
29 let displaySize = Math.min( this.scene.width, this.scene.height );
30 let moveRY = moveX / displaySize * Math.PI * Zdog.TAU;
31 let moveRX = moveY / displaySize * Math.PI * Zdog.TAU;
32 this.scene.rotate.x = this.rotateStartX - moveRX;
33 this.scene.rotate.y = this.rotateStartY - moveRY;
34}
35
36function Zfetch( options ) {
37 this.init( options || {} );
38}
39
40Zfetch.prototype.init = function( options ) {
41 this.click = options.click || noop;
42 this.dragStart = options.dragStart || (options.dragRotate ? this.rotateStart : noop);
43 this.dragMove = options.dragMove || (options.dragRotate ? this.rotateMove : noop);
44 this.dragEnd = options.dragEnd || noop;
45 this.capture = options.capture || false;
46 this.dragRotate = options.dragRotate;
47 this.scene = options.scene;
48 this.startElement = options.scene.element;
49
50 this.bindListeners( this.startElement );
51 this.initGhost( options.scene );
52};
53
54Zfetch.prototype.bindListeners = function( element ) {
55 element = this.getQueryElement( element );
56 if ( !element ) {
57 console.error( 'Zfetch: startElement not found: ' + element );
58 return;
59 }
60 // disable browser gestures
61 // issue #53 on Zdog GitHub
62 element.style.touchAction = 'none';
63 element.addEventListener( 'pointerdown', this );
64};
65
66Zfetch.prototype.getQueryElement = function( element ) {
67 if ( typeof element == 'string' ) {
68 // with string, use query selector
69 return document.querySelector( element );
70 }
71 return element;
72};
73
74Zfetch.prototype.handleEvent = function( event ) {
75 if (DEBUG) console.log(event.type, event);
76 var method = this[ 'on' + event.type ];
77 if ( method ) {
78 method.call( this, event );
79 }
80};
81
82Zfetch.prototype.down = function( event ) {
83 if ( !event.isPrimary ) return;
84
85 if (this.capture) {
86 this.startElement.setPointerCapture( event.pointerId );
87 this.startElement.addEventListener( 'lostpointercapture', this );
88 }
89
90 this.downX = event.pageX;
91 this.downY = event.pageY;
92 this.moveX = 0;
93 this.moveY = 0;
94 this.downId = event.pointerId;
95
96 this.dragEvent = event;
97 this.onpointermove = this.moveDeadzone;
98
99 this.startElement.addEventListener( 'pointermove', this );
100 this.startElement.addEventListener( 'pointerup', this );
101 this.startElement.addEventListener( 'pointercancel', this );
102};
103
104Zfetch.prototype.move = function( event ) {
105 if ( event.pointerId != this.downId ) return;
106
107 this.moveX = event.pageX - this.downX;
108 this.moveY = event.pageY - this.downY;
109 this.dispatchDragMove( event );
110};
111
112Zfetch.prototype.moveDeadzone = function( event ) {
113 if ( event.pointerId != this.downId ) return;
114
115 let moveX = event.pageX - this.downX;
116 let moveY = event.pageY - this.downY;
117
118 var deadzone = 5;
119 if ( event.pointerType == 'mouse' )
120 deadzone = -1;
121 if ( Math.abs( moveX ) > deadzone || Math.abs( moveY ) > deadzone ) {
122 this.dispatchDragStart( this.dragEvent );
123 this.onpointermove = this.move;
124 this.onpointermove( event );
125 }
126}
127
128Zfetch.prototype.up = function( event ) {
129 if ( event.pointerId != this.downId ) return;
130
131 if ( this.capture ) {
132 this.startElement.releasePointerCapture( event.pointerId );
133 this.startElement.removeEventListener( 'lostpointercapture', this );
134 }
135
136 if (DEBUG) console.log("going uuuuup", event);
137
138 if ( this.moveX || this.moveY ) {
139 this.dispatchDragEnd( event );
140 } else {
141 this.dispatchClick( event );
142 }
143
144 this.startElement.removeEventListener( 'pointermove', this );
145 this.startElement.removeEventListener( 'pointerup', this );
146 this.startElement.removeEventListener( 'pointercancel', this );
147};
148
149Zfetch.prototype.onpointerdown = Zfetch.prototype.down;
150Zfetch.prototype.onpointermove = Zfetch.prototype.move;
151Zfetch.prototype.onpointerup =
152Zfetch.prototype.onpointercancel =
153Zfetch.prototype.onlostpointercapture = Zfetch.prototype.up;
154
155Zfetch.prototype.dispatchDragStart = function( event ) {
156 this.dragEvent = null;
157 this.dispatch( "dragStart", event );
158};
159
160Zfetch.prototype.dispatchDragMove = function( event ) {
161 this.dispatch( "dragMove", event );
162};
163
164Zfetch.prototype.dispatchDragEnd = function( event ) {
165 this.dispatch( "dragEnd", event );
166};
167
168Zfetch.prototype.dispatchClick = function( event ) {
169 this.dispatch( "click", event );
170};
171
172Zfetch.prototype.dispatch = function( event, pointer ) {
173 if (DEBUG) console.log("dispatching", event, pointer);
174 var moveX = this.moveX;
175 var moveY = this.moveY;
176 var target = this.getTargetShape( pointer );
177 target.element = this.startElement;
178 if (DEBUG) console.log("pointer, target, x, y", pointer, target, moveX, moveY);
179 this[ event ]( pointer, target, moveX, moveY );
180};
181
182Zfetch.prototype.initGhost = function( scene ) {
183 if ( this.scene.isSvg ) {
184 this.initSvgGhost( scene );
185 } else {
186 this.initCanvasGhost( scene );
187 }
188 this.updateGhost();
189};
190
191Zfetch.prototype.initSvgGhost = function() {
192 this.ghostElem = document.createElement( 'svg' );
193 this.ghostElem.setAttribute( 'width', this.scene.element.clientWidth );
194 this.ghostElem.setAttribute( 'height', this.scene.element.clientHeight );
195 /*this.ghostElem.style.display = "none";
196 this.ghostElem = document.body.appendChild( this.ghostElem );*/
197};
198
199Zfetch.prototype.updateGhost = function() {
200 this.ghost = this.scene.copyGraph({ element: this.ghostElem });
201 this.ghost.updateGraph();
202 for ( let i = 0; i < this.ghost.flatGraph.length; i++ ) {
203 const shape = this.ghost.flatGraph[i];
204 shape.twin = this.scene.flatGraph[i];
205 shape.color = "#" + (i).toString(16).padStart(6, "0");
206 let faces = [
207 "leftFace",
208 "rightFace",
209 "topFace",
210 "bottomFace",
211 "frontFace",
212 "rearFace",
213 "backface"
214 ];
215 // override overridden faces
216 for ( let face of faces ) {
217 if ( shape.twin[ face ] )
218 shape[ face ] = shape.color;
219 }
220 }
221 this.ghost.renderGraph();
222};
223
224Zfetch.prototype.getSvgPath = function( root, element ) {
225 const path = [];
226 let current = element;
227
228 while ( current !== root ) {
229 const parent = current.parentElement;
230 const index = Array.from( parent.children ).indexOf( current );
231 path.unshift( index );
232 current = parent;
233 }
234
235 return path;
236};
237
238Zfetch.prototype.getElementByPath = function( root, path ) {
239 let current = root;
240
241 for ( const index of path ) {
242 current = current.children[ index ];
243 }
244
245 return current;
246};
247
248// TODO: This isn't returning correctly for the surface of cylinders.
249Zfetch.prototype.getTargetShape = function( pointer ) {
250 if ( this.scene.isSvg )
251 return this.getTargetShapeSvg( pointer );
252
253 return this.getTargetShapeCanvas( pointer );
254};
255
256Zfetch.prototype.getTargetShapeSvg = function( pointer ) {
257 // happy path: if we're on an svg
258 if ( pointer.target.tagName == "svg" )
259 return this.scene;
260
261 this.updateGhost();
262 let path = this.getSvgPath( this.scene.element, pointer.target );
263 let ghostElem = this.getElementByPath( this.ghostElem, path );
264 let color = ghostElem.getAttribute( "fill" );
265 if ( color == "none" )
266 color = ghostElem.getAttribute( "stroke" );
267 if ( color == "none" )
268 return this.scene;
269 let ghostShape = Array.from( this.ghost.flatGraph ).find( shape => shape.color === color );
270 if ( !ghostShape )
271 return this.scene;
272 return ghostShape.twin;
273};
274
275return Zfetch;
276
277}) );