Input handling library for Zdog.
at main 277 lines 7.7 kB view raw
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}) );