2021-11-03 04:21:58 +02:00
const HOLDING _THRESHOLD _MS = 300 ;
const SCROLL _X _DAMPENING = 0.03 ;
const SCROLL _Y _DAMPENING = 0.09 ;
class TouchpadController {
constructor ( connection ) {
this . currentMoveX = 0 ;
this . currentMoveY = 0 ;
this . lastMoveX = 0 ;
this . lastMoveY = 0 ;
this . shouldResetLastMove = false ;
this . isInHoldingMode = false ;
this . ongoingTouches = { } ;
this . holdModeTimeout = null ;
2021-11-08 00:25:27 +02:00
this . touchpadDiv = null ;
2021-11-03 04:21:58 +02:00
this . connection = connection ;
}
getButtonCode ( button ) {
let buttonCode = 0 ;
if ( button === "right" ) buttonCode = 1 ;
return buttonCode ;
}
// takes a gesture name and a touch list
// adds the gesture name to the touch and returns false if that touch already had that gesture
_gesture ( gestureName , touchList ) {
for ( let i = 0 ; i < touchList . length ; i ++ ) {
const touch = this . ongoingTouches [ touchList [ i ] . identifier ] ;
if ( touch . gestured . includes ( gestureName ) ) return false ;
touch . gestured . push ( gestureName ) ;
}
return true ;
}
_sendRelativeMouseMovement ( dx , dy ) {
if ( dx === 0 && dy === 0 )
return false ;
this . connection . sendMessage ( "r" , [ dx , dy ] ) ;
return true ;
}
_sendRelativeMouseScroll ( dx , dy ) {
if ( dx === 0 && dy === 0 )
return false ;
this . connection . sendMessage ( "s" , [ dx , dy ] ) ;
return true ;
}
_sendMouseButtonDown ( button = "left" ) {
this . connection . sendMessage ( "d" , [ this . getButtonCode ( button ) ] ) ;
}
_sendMouseButtonUp ( button = "left" ) {
this . connection . sendMessage ( "u" , [ this . getButtonCode ( button ) ] ) ;
}
_sendSingleClick ( button = "left" ) {
this . connection . sendMessage ( "c" , [ this . getButtonCode ( button ) ] ) ;
}
2021-11-08 00:25:27 +02:00
mountOn ( element ) {
if ( this . touchpadDiv )
return ; // Already mounted
this . touchpadDiv = document . createElement ( "div" ) ;
this . touchpadDiv . classList . add ( "touchpad" ) ;
element . appendChild ( this . touchpadDiv ) ;
this . touchpadDiv . addEventListener ( "touchmove" , this . onTouchMove . bind ( this ) ) ;
this . touchpadDiv . addEventListener ( "touchend" , this . onTouchEnd . bind ( this ) ) ;
this . touchpadDiv . addEventListener ( "touchstart" , this . onTouchStart . bind ( this ) ) ;
}
unmount ( ) {
if ( ! this . touchpadDiv )
return ; // Not mounted - don't do anything
this . touchpadDiv . parentElement . removeChild ( this . touchpadDiv ) ;
this . touchpadDiv = null ;
2021-11-03 04:21:58 +02:00
}
onTouchMove ( event ) {
const touches = event . changedTouches ;
event . preventDefault ( ) ;
const targetTouch = touches [ 0 ] ;
this . currentMoveX = targetTouch . pageX ;
this . currentMoveY = targetTouch . pageY ;
// When ending a touch and starting a new one in another part of the touchpad,
// the cursor "rubber bands" to that position.
// To solve this, the "last move" parameters are reset when a touch is ended
// (see onTouchEnd())
if ( this . shouldResetLastMove ) {
this . shouldResetLastMove = false ;
this . lastMoveX = this . currentMoveX ;
this . lastMoveY = this . currentMoveY ;
}
const deltaX = this . currentMoveX - this . lastMoveX ;
const deltaY = this . currentMoveY - this . lastMoveY ;
this . lastMoveX = this . currentMoveX ;
this . lastMoveY = this . currentMoveY ;
// if two touches moved at the same time, assume scrolling intent
// if _sendRelativeMouseScroll or _sendRelativeMouseMovement return true, it means that the delta values are non-zero and a packet has been sent to the server
// in that case, the touches will be marked as moved
let shouldMarkTouchesAsMoved = false ;
if ( touches . length === 2 ) {
if (
this . _sendRelativeMouseScroll ( deltaX * SCROLL _X _DAMPENING , deltaY * SCROLL _Y _DAMPENING )
) {
shouldMarkTouchesAsMoved = true ;
}
} else if ( touches . length === 3 && this . _gesture ( "GESTURE_RIGHT_CLICK" , touches ) ) {
shouldMarkTouchesAsMoved = true ;
this . _sendSingleClick ( "right" ) ;
} else {
if (
this . _sendRelativeMouseMovement ( deltaX , deltaY )
) {
shouldMarkTouchesAsMoved = true ;
}
}
if ( shouldMarkTouchesAsMoved ) {
for ( let i = 0 ; i < touches . length ; i ++ ) {
this . ongoingTouches [ touches [ i ] . identifier ] . hasMoved = true ;
}
}
}
onTouchEnd ( event ) {
const changedTouches = event . changedTouches ;
this . shouldResetLastMove = true ;
if ( changedTouches . length === 1 ) {
// This is a single tap - left click
if ( ! this . ongoingTouches [ changedTouches [ 0 ] . identifier ] . hasMoved ) {
this . _sendSingleClick ( "left" ) ;
// We were in "holding mode" and now that touch event has ended,
// thus we have to stop holding the left click button
} else if ( this . isInHoldingMode ) {
this . _sendMouseButtonUp ( "left" ) ;
this . isInHoldingMode = false ;
}
}
// remove all ended touches
for ( let i = 0 ; i < changedTouches . length ; i ++ ) {
const touch = changedTouches [ i ] ;
this . ongoingTouches [ touch . identifier ] = null ;
delete this . ongoingTouches [ touch . identifier ] ;
}
}
onTouchStart ( event ) {
const changedTouches = event . changedTouches ;
// Clear the hold mode time out if another touch begins
if ( this . holdModeTimeout )
clearTimeout ( this . holdModeTimeout ) ;
// If the touch is still unmoved and held for a certain amount of time,
// we will enter "holding mode" which keeps the mouse button held while dragging
// allowing you to, for example, select text or drag a scrollbar
if ( changedTouches . length === 1 ) {
const targetTouch = changedTouches [ 0 ] ;
this . holdModeTimeout = setTimeout ( ( ) => {
if ( this . ongoingTouches [ targetTouch . identifier ] && ! this . ongoingTouches [ targetTouch . identifier ] . hasMoved ) {
this . isInHoldingMode = true ;
this . _sendMouseButtonDown ( "left" ) ;
}
} , HOLDING _THRESHOLD _MS ) ;
}
for ( let i = 0 ; i < changedTouches . length ; i ++ ) {
const touch = changedTouches [ i ] ;
this . ongoingTouches [ touch . identifier ] = {
identifier : touch . identifier ,
clientX : touch . clientX ,
clientY : touch . clientY ,
hasMoved : false ,
gestured : [ ]
} ;
}
}
}
export default TouchpadController ;