import { SaveManager , GlobalSaveManager } from "./SaveManager" ; import { GlobalGrabManager } from './GrabManager' ; function isModifier ( key ) { switch ( key ) { case 'ShiftLeft' : case 'ShiftRight' : case 'AltLeft' : case 'AltRight' : return true ; default : return false ; } } export type Listener = ( inputManager : InputManager ) => void ; const enum TouchGesture { None , Scroll , Pinch } export const enum DraggingMode { None , Dragging , PointerLocked } export default class InputManager { public invertY = false ; public invertX = false ; public toplevel : HTMLElement ; public keysDown : Map < string , boolean > ; public mouseX : number = - 1 ; public mouseY : number = - 1 ; public dx : number = 0 ; public dy : number = 0 ; public dz : number = 0 ; public buttons : number = 0 ; public ondraggingmodechanged : ( ( ) => void ) | null = null ; private scrollListeners : Listener [ ] = [ ] ; private usePointerLock : boolean = true ; public isInteractive : boolean = true ; private touchGesture : TouchGesture = TouchGesture . None ; private prevTouchX : number = 0 ; private prevTouchY : number = 0 ; private prevPinchDist : number = 0 ; private dTouchX : number = 0 ; private dTouchY : number = 0 ; private dPinchDist : number = 0 ; private releaseOnMouseUp : boolean = true ; constructor ( toplevel ) { document . body . tabIndex = - 1 ; this . toplevel = toplevel ; this . toplevel . tabIndex = - 1 ; this . keysDown = new Map < string , boolean > ( ) ; document . addEventListener ( 'keydown' , this . _onKeyDown , { capture : true } ) ; document . addEventListener ( 'keyup' , this . _onKeyUp , { capture : true } ) ; window . addEventListener ( 'blur' , this . _onBlur ) ; window . addEventListener ( 'contextmenu' , ( e ) => { e . preventDefault ( ) ; } ) ; this . toplevel . addEventListener ( 'wheel' , this . _onWheel , { passive : false } ) ; this . toplevel . addEventListener ( 'mousedown' , ( e ) => { if ( ! this . isInteractive ) return ; this . buttons = e . buttons ; GlobalGrabManager . takeGrab ( this , e , { takePointerLock : this . usePointerLock , useGrabbingCursor : true , releaseOnMouseUp : this . releaseOnMouseUp } ) ; if ( this . ondraggingmodechanged !== null ) this . ondraggingmodechanged ( ) ; } ) ; this . toplevel . addEventListener ( 'mousemove' , ( e ) => { this . mouseX = e . clientX * window . devicePixelRatio ; this . mouseY = e . clientY * window . devicePixelRatio ; } ) ; this . toplevel . addEventListener ( 'mouseup' , ( e ) => { this . buttons = e . buttons ; } ) ; this . toplevel . addEventListener ( 'touchstart' , this . _onTouchChange ) ; this . toplevel . addEventListener ( 'touchend' , this . _onTouchChange ) ; this . toplevel . addEventListener ( 'touchcancel' , this . _onTouchChange ) ; this . toplevel . addEventListener ( 'touchmove' , this . _onTouchMove ) ; this . afterFrame ( ) ; GlobalSaveManager . addSettingListener ( 'InvertX' , ( saveManager , key ) => { this . invertX = saveManager . loadSetting < boolean > ( key , false ) ; } ) ; GlobalSaveManager . addSettingListener ( 'InvertY' , ( saveManager , key ) => { this . invertY = saveManager . loadSetting < boolean > ( key , false ) ; } ) ; } public addScrollListener ( listener ) { this . scrollListeners . push ( listener ) ; } public getMouseDeltaX ( ) { return this . dx ; } public getMouseDeltaY ( ) { return this . dy ; } public getTouchDeltaX ( ) { return this . touchGesture === TouchGesture . Pinch ? this . dTouchX : 0 ; } public getTouchDeltaY ( ) { return this . touchGesture === TouchGesture . Pinch ? this . dTouchY : 0 ; } public getPinchDeltaDist ( ) { return this . touchGesture === TouchGesture . Pinch ? this . dPinchDist : 0 ; } public isKeyDownEventTriggered ( key ) { return ! ! this . keysDown . get ( key ) ; } public isKeyDown ( key ) { return this . keysDown . has ( key ) ; } public getDraggingMode ( ) { if ( this . touchGesture !== TouchGesture . None ) return DraggingMode . Dragging ; const grabOptions = GlobalGrabManager . getGrabListenerOptions ( this ) ; if ( grabOptions !== null ) return grabOptions . takePointerLock ? DraggingMode . PointerLocked : DraggingMode . Dragging ; return DraggingMode . None ; } public isDragging ( ) { return this . getDraggingMode ( ) !== DraggingMode . None ; } public afterFrame ( ) { this . dx = 0 ; this . dy = 0 ; this . dz = 0 ; this . dTouchX = 0 ; this . dTouchY = 0 ; this . dPinchDist = 0 ; this . keysDown . forEach ( ( v , k ) => { this . keysDown . set ( k , false ) ; } ) ; } private _hasFocus ( ) { return document . activeElement === document . body || document . activeElement === this . toplevel ; } private _callScrollListeners ( ) { for ( let i = 0 ; i < this . scrollListeners . length ; i ++ ) this . scrollListeners [ i ] ( this ) ; } private _onKeyDown = ( e ) => { if ( isModifier ( e . code ) ) { e . preventDefault ( ) ; } else { if ( ! this . _hasFocus ( ) ) return ; } this . keysDown . set ( e . code , ! e . repeat ) ; } ; private _onKeyUp = ( e ) => { this . keysDown . delete ( e . code ) ; } ; private _onBlur = ( ) => { this . keysDown . clear ( ) ; } ; private _onWheel = ( e ) => { e . preventDefault ( ) ; this . dz += Math . sign ( e . deltaY ) * - 4 ; this . _callScrollListeners ( ) ; } ; private _getScaledTouches ( touches ) { const result = [ ] ; const scale = 1000 / Math . max ( 1 , Math . min ( this . toplevel . clientWidth , this . toplevel . clientHeight ) ) ; for ( let i = 0 ; i < touches . length ; i ++ ) { result . push ( { x : touches [ i ] . clientX * scale , y : touches [ i ] . clientY * scale } ) ; } return result ; } private _getPinchValues ( touches ) { const scaledTouches = this . _getScaledTouches ( touches ) ; return { x : ( scaledTouches [ 0 ] . x + scaledTouches [ 1 ] . x ) / 2 , y : ( scaledTouches [ 0 ] . y + scaledTouches [ 1 ] . y ) / 2 , dist : Math . hypot ( scaledTouches [ 0 ] . x - scaledTouches [ 1 ] . x , scaledTouches [ 0 ] . y - scaledTouches [ 1 ] . y ) , } ; } private _onTouchChange = ( e ) => { if ( ! this . isInteractive ) return ; e . preventDefault ( ) ; if ( e . touches . length == 1 ) { const scaledTouches = this . _getScaledTouches ( e . touches ) ; this . touchGesture = TouchGesture . Scroll ; this . prevTouchX = scaledTouches [ 0 ] . x ; this . prevTouchY = scaledTouches [ 0 ] . y ; this . dTouchX = 0 ; this . dTouchY = 0 ; } else if ( e . touches . length == 2 ) { const pinchValues = this . _getPinchValues ( e . touches ) ; this . touchGesture = TouchGesture . Pinch ; this . prevTouchX = pinchValues . x ; this . prevTouchY = pinchValues . y ; this . prevPinchDist = pinchValues . dist ; this . dTouchX = 0 ; this . dTouchY = 0 ; this . dPinchDist = 0 ; } else { this . touchGesture = TouchGesture . None ; } } ; private _onTouchMove = ( e ) => { if ( ! this . isInteractive ) return ; e . preventDefault ( ) ; if ( e . touches . length == 1 ) { const scaledTouches = this . _getScaledTouches ( e . touches ) ; this . touchGesture = TouchGesture . Scroll ; this . dTouchX = scaledTouches [ 0 ] . x - this . prevTouchX ; this . dTouchY = scaledTouches [ 0 ] . y - this . prevTouchY ; this . onMotion ( this . dTouchX , this . dTouchY ) ; this . prevTouchX = scaledTouches [ 0 ] . x ; this . prevTouchY = scaledTouches [ 0 ] . y ; } else if ( e . touches . length == 2 ) { const pinchValues = this . _getPinchValues ( e . touches ) ; this . touchGesture = TouchGesture . Pinch ; this . dTouchX = pinchValues . x - this . prevTouchX ; this . dTouchY = pinchValues . y - this . prevTouchY ; this . dPinchDist = pinchValues . dist - this . prevPinchDist ; this . prevTouchX = pinchValues . x ; this . prevTouchY = pinchValues . y ; this . prevPinchDist = pinchValues . dist ; } else { this . touchGesture = TouchGesture . None ; } } ; public onMotion ( dx , dy ) { if ( Math . abs ( dx ) > 200 || Math . abs ( dy ) > 200 ) return ; this . dx += dx ; this . dy += dy ; } public onGrabReleased ( ) { this . buttons = 0 ; if ( this . ondraggingmodechanged !== null ) this . ondraggingmodechanged ( ) ; } public reset ( ) { this . isInteractive = true ; } }