//----------------------------------------------------------------------------- // Copyright (c) 2013 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platformAndroid/platformAndroid.h" #include "platformAndroid/AndroidUtil.h" #include "platform/platformInput.h" #include "platform/event.h" #include "console/console.h" #include "game/gameInterface.h" #include "string/unicode.h" #include "gui/guiCanvas.h" // just some random number 50, we'll get a proper value later #define Android_DOUBLE_CLICK_TIME ( 50 * 60.0f * 1000.0f) // Static class variables: InputManager* Input::smManager; bool Input::smActive; CursorManager* Input::smCursorManager = 0; bool gInputEnabled = false; bool gMouseEnabled = false; bool gKBEnabled = false; bool gMouseActive = false; bool gKBActive = false; //------------------------------------------------------------------------------ // Helper functions. Should migrate to an InputManager object at some point. bool enableKeyboard(void); void disableKeyboard(void); bool activateKeyboard(void); void deactivateKeyboard(void); bool enableMouse(void); void disableMouse(void); bool activateMouse(void); void deactivateMouse(void); static void fillAsciiTable(); struct touchEvent { S32 number; S32 x; S32 y; touchEvent( S32 a, S32 b, S32 c ) { number = a; x = b; y = c; } }; Vector TouchMoveEvents;// to make sure we don't have multiple events per frame Vector TouchDownEvents; Vector TouchUpEvents; // EFM - BEGIN TOUCH CHANGES #define MAX_TOUCH_EVENTS 5 struct touchTracker { S32 lastX; S32 lastY; Vector downEvents; Vector moveEvents; Vector upEvents; }; touchTracker CombinedTouchEvents[MAX_TOUCH_EVENTS]; struct touchCorrelation { S32 lastX; S32 lastY; }; touchCorrelation lastTouches[MAX_TOUCH_EVENTS]; // EFM - END TOUCH CHANGES //Luma: Tap support Vector TouchTapEvents; int processMultipleTouches(); //------------------------------------------------------------------------------ // // This function gets the standard ASCII code corresponding to our key code // and the existing modifier key state. // //------------------------------------------------------------------------------ struct AsciiData { struct KeyData { U16 ascii; bool isDeadChar; }; KeyData upper; KeyData lower; KeyData goofy; }; #define NUM_KEYS ( KEY_OEM_102 + 1 ) #define KEY_FIRST KEY_ESCAPE static AsciiData AsciiTable[NUM_KEYS]; void Input::enableMouse() { // Do nothing on Android } void Input::disableMouse() { // Do nothing on Android } void Input::enableKeyboard() { // Do nothing on Android } void Input::disableKeyboard() { // Do nothing on Android } bool Input::isMouseEnabled() { return true; } bool Input::isKeyboardEnabled() { return false; } //-------------------------------------------------------------------------- void Input::init() { Con::printf( "Input Init:" ); destroy(); smManager = NULL; //smManager = new AndroidInputManager(); smActive = false; // stop the double-cursor thing Con::setBoolVariable("$pref::Gui::noClampTorqueCursorToWindow", true); // enable main input Input::enable(); // Startup the Cursor Manager if(!smCursorManager) { smCursorManager = new CursorManager(); if(smCursorManager) { // Add the arrow cursor to the stack smCursorManager->pushCursor(CursorManager::curArrow); } else { Con::printf(" Cursor Manager not enabled."); } } for(int i = 0 ; i < MAX_TOUCH_EVENTS; i++ ) { lastTouches[i].lastX = -1; lastTouches[i].lastY = -1; } Con::printf( "" ); } //------------------------------------------------------------------------------ ConsoleFunction( isJoystickDetected, bool, 1, 1, "Always false on the Android." ) { /* argc; argv; return( DInputDevice::joystickDetected() ); */ return(false); } //------------------------------------------------------------------------------ ConsoleFunction( getJoystickAxes, const char*, 2, 2, "(handle instance)" ) { return( "" ); } //------------------------------------------------------------------------------ static void fillAsciiTable() { } //------------------------------------------------------------------------------ U16 Input::getKeyCode( U16 asciiCode ) { U16 keyCode = 0; U16 i; // This is done three times so the lowerkey will always // be found first. Some foreign keyboards have duplicate // chars on some keys. for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) { if ( AsciiTable[i].lower.ascii == asciiCode ) { keyCode = i; break; }; } for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) { if ( AsciiTable[i].upper.ascii == asciiCode ) { keyCode = i; break; }; } for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) { if ( AsciiTable[i].goofy.ascii == asciiCode ) { keyCode = i; break; }; } return( keyCode ); } //------------------------------------------------------------------------------ U16 Input::getAscii( U16 keyCode, KEY_STATE keyState ) { if ( keyCode >= NUM_KEYS ) return 0; switch ( keyState ) { case STATE_LOWER: return AsciiTable[keyCode].lower.ascii; case STATE_UPPER: return AsciiTable[keyCode].upper.ascii; case STATE_GOOFY: return AsciiTable[keyCode].goofy.ascii; default: return(0); } } //------------------------------------------------------------------------------ void Input::destroy() { // turn us off. if (gInputEnabled) disable(); if ( smManager && smManager->isEnabled() ) { smManager->disable(); delete smManager; smManager = NULL; } } //------------------------------------------------------------------------------ bool Input::enable() { Con::printf( "[]Input::enable." ); gInputEnabled = true; if ( smManager && !smManager->isEnabled() ) return( smManager->enable() ); enableMouse(); //enableKeyboard(); return( gInputEnabled ); } //------------------------------------------------------------------------------ void Input::disable() { Con::printf( "[]Input::disable." ); gInputEnabled = false; if ( smManager && smManager->isEnabled() ) smManager->disable(); disableMouse(); //disableKeyboard(); } //------------------------------------------------------------------------------ void Input::activate() { smActive = true; enableMouse(); // enableKeyboard(); } //------------------------------------------------------------------------------ void Input::deactivate() { //Con::printf( "[]Input::deactivate." ); deactivateMouse(); //deactivateKeyboard(); smActive = false; } //------------------------------------------------------------------------------ void Input::reactivate() { // don't think mac needs to do anything right now!!!!!! TBD // This is soo hacky... // SetForegroundWindow( winState.appWindow ); // PostMessage( winState.appWindow, WM_ACTIVATE, WA_ACTIVE, NULL ); } //------------------------------------------------------------------------------ bool Input::isEnabled() { if ( smManager ) return smManager->isEnabled(); return(gInputEnabled); } //------------------------------------------------------------------------------ bool Input::isActive() { return smActive; } //------------------------------------------------------------------------------ void Input::process() { //only gets called once per frame, create touches and accelerometer events here //post, then pop each event if(platState.multipleTouchesEnabled) processMultipleTouches(); if (!smActive || !gInputEnabled) return; if (!gMouseEnabled || !gMouseActive) return; if ( smManager && smManager->isEnabled() && smActive ) smManager->process(); } //------------------------------------------------------------------------------ InputManager* Input::getManager() { return( smManager ); } #pragma mark ---- Clipboard functions ---- //----------------------------------------------------------------------------- const char* Platform::getClipboard() { return NULL;//no clipboard on Android } //----------------------------------------------------------------------------- bool Platform::setClipboard(const char *text) { return false;//no clipboard on Android } #pragma mark ---- Cursor Functions ---- //------------------------------------------------------------------------------ void Input::pushCursor(S32 cursorID) { CursorManager* cm = getCursorManager(); if(cm) cm->pushCursor(cursorID); } //------------------------------------------------------------------------------ void Input::popCursor() { CursorManager* cm = getCursorManager(); if(cm) cm->popCursor(); } //------------------------------------------------------------------------------ void Input::refreshCursor() { CursorManager* cm = getCursorManager(); if(cm) cm->refreshCursor(); } #pragma mark ---- DoubleClick Functions ---- //------------------------------------------------------------------------------ U32 Input::getDoubleClickTime() { return Android_DOUBLE_CLICK_TIME; } //------------------------------------------------------------------------------ S32 Input::getDoubleClickWidth() { // this is an arbitrary value. return 10; } //------------------------------------------------------------------------------ S32 Input::getDoubleClickHeight() { return getDoubleClickWidth(); } #pragma mark - //------------------------------------------------------------------------------ bool enableKeyboard() { if ( !gInputEnabled ) return( false ); if ( gKBEnabled && gKBActive ) return( true ); gKBEnabled = true; if ( Input::isActive() ) gKBEnabled = activateKeyboard(); if ( gKBEnabled ) { Con::printf( "Hardware-direct keyboard enabled." ); } else { Con::warnf( "Hardware-direct keyboard failed to enable!" ); } return( gKBEnabled ); } //------------------------------------------------------------------------------ void disableKeyboard() { if ( !gInputEnabled || !gKBEnabled ) return; deactivateKeyboard(); gKBEnabled = false; Con::printf( "Hardware-direct keyboard disabled." ); } //------------------------------------------------------------------------------ bool activateKeyboard() { if ( !gInputEnabled || !Input::isActive() || !gKBEnabled ) return( false ); gKBActive = true; return( gKBActive ); } //------------------------------------------------------------------------------ void deactivateKeyboard() { if ( gInputEnabled && gKBActive ) { gKBActive = false; } } //------------------------------------------------------------------------------ bool enableMouse() { if ( !gInputEnabled ) return( false ); if ( gMouseEnabled && gMouseActive ) return( true ); gMouseEnabled = activateMouse(); return( gMouseEnabled ); } //------------------------------------------------------------------------------ void disableMouse() { if ( !gInputEnabled || !gMouseEnabled ) return; deactivateMouse(); gMouseEnabled = false; bool hwMouse = false; Con::printf( "%s disabled", hwMouse?"Hardware-direct mouse":"Basic mouse capture"); } //------------------------------------------------------------------------------ bool activateMouse() { if ( !gInputEnabled || !Input::isActive() || !gMouseEnabled ) return( false ); if (gMouseActive) return(true); gMouseActive = true; return( gMouseActive ); } //------------------------------------------------------------------------------ void deactivateMouse() { if ( !gInputEnabled || !gMouseActive ) return; gMouseActive = false; } //------------------------------------------------------------------------------ ConsoleFunction( enableMouse, bool, 1, 1, "enableMouse()" ) { return( enableMouse() ); } //------------------------------------------------------------------------------ ConsoleFunction( disableMouse, void, 1, 1, "disableMouse()" ) { disableMouse(); } //------------------------------------------------------------------------------ void printInputState(void) { if ( gInputEnabled ) { Con::printf( "Low-level input system is enabled." ); Con::printf( "- Keyboard is %sabled and %sactive.", gKBEnabled ? "en" : "dis", gKBActive ? "" : "in" ); Con::printf( "- Mouse is %sabled and %sactive.", gMouseEnabled ? "en" : "dis", gMouseActive ? "" : "in" ); /* Con::printf( "- Joystick is %sabled and %sactive.", gJoystickEnabled() ? "en" : "dis", gJoystickActive() ? "" : "in" ); */ } else { Con::printf( "Low-level input system is disabled." ); } } //------------------------------------------------------------------------------ ConsoleFunction( echoInputState, void, 1, 1, "echoInputState()" ) { printInputState(); } //------------------------------------------------------------------------------ ConsoleFunction( toggleInputState, void, 1, 1, "toggleInputState()" ) { if (gInputEnabled) Input::disable(); else Input::enable(); printInputState(); } //------------------------------------------------------------------------------ ConsoleFunction( deactivateKeyboard, void, 1, 1, "deactivateKeyboard();") { // these are only useful on the windows side. They deal with some vagaries of win32 DirectInput. } ConsoleFunction( activateKeyboard, void, 1, 1, "activateKeyboard();") { // these are only useful on the windows side. They deal with some vagaries of win32 DirectInput. } //------------------------------------------------------------------------------ void Input::setCursorPos(S32 x, S32 y) { //this gets called from GuiCanvas to set the game mouse cursor } int processMultipleTouches() { char posX[256], posY[256], temp[10], touchNums[256]; dMemset(posX, 0, sizeof(posX)); dMemset(posY, 0, sizeof(posY)); dMemset(touchNums, 0, sizeof(touchNums)); touchEvent *currentEvent; //Down Events int numTouchDownEvents = TouchDownEvents.size(); while( TouchDownEvents.size() > 0 ) { currentEvent = &TouchDownEvents.last(); dItoa( currentEvent->x, temp ); dStrcat( posX, temp ); dStrcat( posX, " " ); dItoa( currentEvent->y, temp ); dStrcat( posY, temp ); dStrcat( posY, " " ); dItoa( currentEvent->number, temp ); dStrcat( touchNums, temp ); dStrcat( touchNums, " " ); TouchDownEvents.pop_back(); } dItoa( numTouchDownEvents, temp ); if( numTouchDownEvents > 0 ) { InputEvent touchEvent; touchEvent.deviceInst = 0; touchEvent.objInst = SI_TOUCHDOWN; touchEvent.deviceType = ScreenTouchDeviceType; touchEvent.action = SI_TOUCH; touchEvent.objType = SI_TOUCHDOWN; dStrcpy(touchEvent.fingersX, posX); dStrcpy(touchEvent.fingersY, posY); dStrcpy(touchEvent.fingerIDs, touchNums); touchEvent.modifier = 0; Game->postEvent(touchEvent); } // Deprecated in 1.5 //if( numTouchDownEvents > 0 ) // Con::executef( 4, "onAndroidTouchDown", touchNums , posX, posY ); //Move events int numTouchMoveEvents = TouchMoveEvents.size(); while( TouchMoveEvents.size() > 0 ) { currentEvent = &TouchMoveEvents.last(); dItoa( currentEvent->x, temp ); dStrcat( posX, temp ); dStrcat( posX, " " ); dItoa( currentEvent->y, temp ); dStrcat( posY, temp ); dStrcat( posY, " " ); dItoa( currentEvent->number, temp ); dStrcat( touchNums, temp ); dStrcat( touchNums, " " ); TouchMoveEvents.pop_back(); } dItoa( numTouchMoveEvents, temp ); if( numTouchMoveEvents > 0 ) { InputEvent touchEvent; touchEvent.deviceInst = 0; touchEvent.objInst = SI_TOUCHMOVE; touchEvent.deviceType = ScreenTouchDeviceType; touchEvent.action = SI_TOUCH; touchEvent.objType = SI_TOUCHMOVE; dStrcpy(touchEvent.fingersX, posX); dStrcpy(touchEvent.fingersY, posY); dStrcpy(touchEvent.fingerIDs, touchNums); touchEvent.modifier = 0; Game->postEvent(touchEvent); } // Deprecated in 1.5 -MP //if( numTouchMoveEvents > 0 ) // Con::executef( 4, "onAndroidTouchMove", touchNums , posX, posY ); //Up events int numTouchUpEvents = TouchUpEvents.size(); dMemset(posX, 0, sizeof(posX)); dMemset(posY, 0, sizeof(posY)); dMemset(touchNums, 0, sizeof(touchNums)); while( TouchUpEvents.size() > 0 ) { currentEvent = &TouchUpEvents.last(); dItoa( currentEvent->x, temp ); dStrcat( posX, temp ); dStrcat( posX, " " ); dItoa( currentEvent->y, temp ); dStrcat( posY, temp ); dStrcat( posY, " " ); dItoa( currentEvent->number, temp ); dStrcat( touchNums, temp ); dStrcat( touchNums, " " ); lastTouches[currentEvent->number].lastX = -1; lastTouches[currentEvent->number].lastY = -1; int x; x = -1; lastTouches[currentEvent->number].lastX = -1; lastTouches[currentEvent->number].lastY = -1; TouchUpEvents.pop_back(); } dItoa( numTouchUpEvents, temp ); if( numTouchUpEvents > 0 ) { InputEvent touchEvent; touchEvent.deviceInst = 0; touchEvent.objInst = SI_TOUCHUP; touchEvent.deviceType = ScreenTouchDeviceType; touchEvent.action = SI_TOUCH; touchEvent.objType = SI_TOUCHUP; dStrcpy(touchEvent.fingersX, posX); dStrcpy(touchEvent.fingersY, posY); dStrcpy(touchEvent.fingerIDs, touchNums); touchEvent.modifier = 0; Game->postEvent(touchEvent); } // Deprecated in 1.5 -MP //if( numTouchUpEvents > 0 ) // Con::executef( 4, "onAndroidTouchUp", touchNums , posX, posY ); //Luma: Tap support posX[0] = '\0'; posY[0] = '\0'; int numTapEvents = TouchTapEvents.size(); while( TouchTapEvents.size() > 0 ) { currentEvent = &TouchTapEvents.last(); sprintf( temp, "%d ", currentEvent->x ); dStrcat( posX, temp ); sprintf( temp, "%d ", currentEvent->y ); dStrcat( posY, temp ); TouchTapEvents.pop_back(); } sprintf( temp, "%d", numTapEvents ); //if( numTapEvents > 0 ) // Con::executef( 4, "onAndroidTouchTap", temp , posX, posY ); return numTouchDownEvents + numTouchMoveEvents + numTouchUpEvents; } //we want these to only be called once per frame!! bool createMouseMoveEvent( S32 touchNumber, S32 x, S32 y, S32 lastX, S32 lastY ) //EFM { S32 currentSlot = -1; if (Canvas == NULL) return false; for( int i = 0 ; (i < MAX_TOUCH_EVENTS) && (currentSlot == -1) ; i++ ) { if( (lastX == lastTouches[i].lastX ) && (lastY == lastTouches[i].lastY ) ) { currentSlot = i; } } if( currentSlot == -1 ) return false; ScreenTouchEvent event; event.xPos = x; event.yPos = y; event.action = SI_MOVE; event.touchID = currentSlot; event.numTouches = 0; //Luma: Mouse not moving (no hover for mouse fingers!) Canvas->setCursorPos( Point2I( x, y ) ); if( currentSlot != -1 ) { lastTouches[currentSlot].lastX = x; lastTouches[currentSlot].lastY = y; } TouchMoveEvents.push_back( touchEvent( currentSlot, x, y ) ); Game->postEvent(event); return true;//return false if we get bad values or something } bool createMouseDownEvent( S32 touchNumber, S32 x, S32 y, U32 numTouches ) { S32 vacantSlot = -1; if (Canvas == NULL) return false; for( int i = 0 ; (i < MAX_TOUCH_EVENTS) && (vacantSlot == -1) ; i++ ) { if( lastTouches[i].lastX == -1 ) { vacantSlot = i; } } if( vacantSlot == -1 ) return false; ScreenTouchEvent event; event.xPos = x; event.yPos = y; event.touchID = vacantSlot; event.action = SI_MAKE; event.numTouches = numTouches; //Luma: Update position Canvas->setCursorPos( Point2I( x, y ) ); if( vacantSlot != -1 ) { lastTouches[vacantSlot].lastX = x; lastTouches[vacantSlot].lastY = y; } TouchDownEvents.push_back( touchEvent( vacantSlot, x, y ) ); Game->postEvent(event); return true;//return false if we get bad values or something } bool createMouseUpEvent( S32 touchNumber, S32 x, S32 y, S32 lastX, S32 lastY, U32 numTouches ) //EFM { S32 currentSlot = -1; for( int i = 0 ; (i < MAX_TOUCH_EVENTS) && (currentSlot == -1) ; i++ ) { if(( (x == lastTouches[i].lastX) && (y == lastTouches[i].lastY )) || ( (lastX == lastTouches[i].lastX ) && (lastY == lastTouches[i].lastY ))) { currentSlot = i; } } if( currentSlot == -1 ) return false; ScreenTouchEvent event; event.xPos = x; event.yPos = y; event.action = SI_BREAK; event.touchID = currentSlot; event.numTouches = numTouches; TouchUpEvents.push_back( touchEvent( currentSlot, x, y ) ); Game->postEvent(event); return true;//return false if we get bad values or something } void clearPendingMultitouchEvents( void ) //EFM { for( int i = 0 ; i < MAX_TOUCH_EVENTS ; i++ ) { lastTouches[i].lastX = -1; lastTouches[i].lastY = -1; } TouchMoveEvents.clear(); TouchDownEvents.clear(); TouchUpEvents.clear(); } //Luma: Tap support void createMouseTapEvent( S32 nbrTaps, S32 x, S32 y ) { TouchTapEvents.push_back( touchEvent( nbrTaps, x, y ) ); }