/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ // Allow use of stuff in #define FORBIDDEN_SYMBOL_EXCEPTION_time_h // // Disable printf override in common/forbidden.h to avoid // clashes with log.h from the Android SDK. // That header file uses // __attribute__ ((format(printf, 3, 4))) // which gets messed up by our override mechanism; this could // be avoided by either changing the Android SDK to use the equally // legal and valid // __attribute__ ((format(__printf__, 3, 4))) // or by refining our printf override to use a varadic macro // (which then wouldn't be portable, though). // Anyway, for now we just disable the printf override globally // for the Android port #define FORBIDDEN_SYMBOL_EXCEPTION_printf #include "backends/platform/android/android.h" #include "backends/platform/android/jni-android.h" #include "backends/platform/android/touchcontrols.h" #include "common/file.h" #include "graphics/svg.h" #define SVG_WIDTH 384 #define SVG_HEIGHT 256 #define BG_WIDTH 128 #define BG_HEIGHT 128 #define ARROW_DEAD 21 #define ARROW_SUPER 42 #define ARROW_END 64 #define ARROW_WIDTH 64 #define ARROW_SEMI_HEIGHT 52 #define ARROW_HEIGHT 64 #define ARROW_HL_LEFT 0 #define ARROW_HL_TOP 128 #define BUTTON_DEAD 12 #define BUTTON_END 64 #define BUTTON_WIDTH 64 #define BUTTON_HEIGHT 64 #define BUTTON_HL_LEFT 128 #define BUTTON_HL_TOP 128 #define BUTTON2_HL_LEFT 256 #define BUTTON2_HL_TOP 128 #define ALPHA_OPAQUE 255 #define ZOMBIE_TIMEOUT 500 // ms // The scale factor is stored as a fixed point 30.2 bits // This avoids floating point operations #define SCALE_FACTOR_FXP 4 // gamepad.svg was designed with a basis of 128x128 sized widget // As it's too small on screen, we apply a factor of 1.5x // It can be tuned here and in SVG viewBox without changing anything else #define SVG_UNSCALED(x) ((x) * 3 / 2) #define SVG_SQ_UNSCALED(x) ((x * x) * 9 / 4) #define SVG_SCALED(x) (SVG_UNSCALED(x) * _scale) #define SCALED_PIXELS(x) ((x) / SCALE_FACTOR_FXP) #define SVG_PIXELS(x) SCALED_PIXELS(SVG_SCALED(x)) #define FUNC_SVG_SQ_SCALED(x) (SVG_SQ_UNSCALED(x) * parent->_scale2) TouchControls::TouchControls() : _drawer(nullptr), _screen_width(0), _screen_height(0), _svg(nullptr), _zombieCount(0), _scale(0), _scale2(0) { _functions[kFunctionLeft] = new FunctionLeft(this); _functions[kFunctionRight] = new FunctionRight(this); _functions[kFunctionCenter] = new FunctionCenter(this); } void TouchControls::init(float scale) { _scale = scale * SCALE_FACTOR_FXP; // As scale is small, this should fit in int _scale2 = _scale * _scale; Common::File stream; if (!stream.open("gamepad.svg")) { error("Failed to fetch gamepad image"); } delete _svg; _svg = new Graphics::SVGBitmap(&stream, SVG_PIXELS(SVG_WIDTH), SVG_PIXELS(SVG_HEIGHT)); } TouchControls::~TouchControls() { delete _svg; for(unsigned int i = 0; i < kFunctionCount; i++) { delete _functions[i]; } } void TouchControls::beforeDraw() { if (!_zombieCount) { return; } // We have zombies, force redraw to render fading out if (_drawer) { _drawer->touchControlNotifyChanged(); } // Check for zombie functions, clear them out if expired unsigned int zombieCount = 0; uint32 now = g_system->getMillis(true); for (uint i = 0; i < kFunctionCount; i++) { Function *func = _functions[i]; if (func->status != kFunctionZombie) { continue; } if (func->lastActivable < now) { func->status = kFunctionInactive; continue; } zombieCount++; } _zombieCount = zombieCount; } TouchControls::FunctionId TouchControls::getFunctionId(int x, int y) { if (_screen_width == 0) { // Avoid divide by 0 error return kFunctionNone; } // Exclude areas reserved for system if ((x < JNI::gestures_insets[0] * SCALE_FACTOR_FXP) || (y < JNI::gestures_insets[1] * SCALE_FACTOR_FXP) || (x >= _screen_width - JNI::gestures_insets[2] * SCALE_FACTOR_FXP) || (y >= _screen_height - JNI::gestures_insets[3] * SCALE_FACTOR_FXP)) { return kFunctionNone; } float xRatio = float(x) / _screen_width; if (xRatio < 0.3) { return kFunctionLeft; } else if (xRatio < 0.7) { return kFunctionCenter; } else { return kFunctionRight; } } void TouchControls::setDrawer(TouchControlsDrawer *drawer, int width, int height) { _drawer = drawer; _screen_width = width * SCALE_FACTOR_FXP; _screen_height = height * SCALE_FACTOR_FXP; if (drawer) { drawer->touchControlInitSurface(*_svg); } } TouchControls::Function *TouchControls::getFunctionFromPointerId(int ptrId) { for (uint i = 0; i < kFunctionCount; i++) { Function *func = _functions[i]; if (func->status != kFunctionActive) { continue; } if (func->pointerId == ptrId) { return func; } } return nullptr; } TouchControls::Function *TouchControls::getZombieFunctionFromPos(int x, int y) { if (!_zombieCount) { return nullptr; } for (uint i = 0; i < kFunctionCount; i++) { Function *func = _functions[i]; if (func->status != kFunctionZombie) { // Already assigned to a finger or dead continue; } if (func->isInside(x, y)) { return func; } } return nullptr; } void TouchControls::draw() { assert(_drawer != nullptr); uint32 now = g_system->getMillis(true); for (uint i = 0; i < kFunctionCount; i++) { Function *func = _functions[i]; uint8 alpha; switch (func->status) { case kFunctionActive: alpha = ALPHA_OPAQUE; break; case kFunctionZombie: if (func->lastActivable < now) { // This function is definitely dead continue; } alpha = (func->lastActivable - now) * ALPHA_OPAQUE / ZOMBIE_TIMEOUT; break; case kFunctionInactive: default: continue; } func->draw(alpha); } } void TouchControls::update(Action action, int ptrId, int x, int y) { x *= SCALE_FACTOR_FXP; y *= SCALE_FACTOR_FXP; if (action == JACTION_DOWN) { Function *func = getZombieFunctionFromPos(x, y); if (!func) { // Finger was not pressed on a zombie function // Determine which function it could be FunctionId funcId = getFunctionId(x, y); if (funcId != kFunctionNone) { func = _functions[funcId]; } if (!func) { // No function for this finger return; } if (func->status == kFunctionActive) { // Another finger is already on this function return; } // When zombie, we reuse the old start coordinates // but not when starting over func->reset(); func->startX = x; func->startY = y; } func->status = kFunctionActive; func->pointerId = ptrId; func->currentX = x; func->currentY = y; int dX = x - func->startX; int dY = y - func->startY; func->touch(dX, dY, action); if (_drawer) { _drawer->touchControlNotifyChanged(); } } else if (action == JACTION_MOVE) { Function *func = getFunctionFromPointerId(ptrId); if (!func) { return; } func->currentX = x; func->currentY = y; int dX = x - func->startX; int dY = y - func->startY; func->touch(dX, dY, action); if (_drawer) { _drawer->touchControlNotifyChanged(); } } else if (action == JACTION_UP) { Function *func = getFunctionFromPointerId(ptrId); if (!func) { return; } func->currentX = x; func->currentY = y; int dX = x - func->startX; int dY = y - func->startY; func->touch(dX, dY, action); func->status = kFunctionZombie; func->lastActivable = g_system->getMillis(true) + ZOMBIE_TIMEOUT; if (_drawer) { _drawer->touchControlNotifyChanged(); } _zombieCount++; } else if (action == JACTION_CANCEL) { for (uint i = 0; i < kFunctionCount; i++) { Function *func = _functions[i]; if (func->status == kFunctionActive) { func->touch(0, 0, action); } func->reset(); } if (_drawer) { _drawer->touchControlNotifyChanged(); } _zombieCount = 0; } } void TouchControls::buttonDown(Common::JoystickButton jb) { if (jb == Common::JOYSTICK_BUTTON_INVALID) { return; } //LOGD("TouchControls::buttonDown: %d", jb); Common::Event ev; ev.type = Common::EVENT_JOYBUTTON_DOWN; ev.joystick.button = jb; dynamic_cast(g_system)->pushEvent(ev); } void TouchControls::buttonUp(Common::JoystickButton jb) { if (jb == Common::JOYSTICK_BUTTON_INVALID) { return; } //LOGD("TouchControls::buttonUp: %d", jb); Common::Event ev; ev.type = Common::EVENT_JOYBUTTON_UP; ev.joystick.button = jb; dynamic_cast(g_system)->pushEvent(ev); } void TouchControls::buttonPress(Common::JoystickButton jb) { if (jb == Common::JOYSTICK_BUTTON_INVALID) { return; } //LOGD("TouchControls::buttonPress: %d", jb); Common::Event ev1, ev2; ev1.type = Common::EVENT_JOYBUTTON_DOWN; ev1.joystick.button = jb; ev2.type = Common::EVENT_JOYBUTTON_UP; ev2.joystick.button = jb; dynamic_cast(g_system)->pushEvent(ev1, ev2); } void TouchControls::drawSurface(uint8 alpha, int x, int y, int offX, int offY, const Common::Rect &clip) const { Common::Rect clip_(SVG_PIXELS(clip.left), SVG_PIXELS(clip.top), SVG_PIXELS(clip.right), SVG_PIXELS(clip.bottom)); _drawer->touchControlDraw(alpha, SCALED_PIXELS(x + SVG_SCALED(offX)), SCALED_PIXELS(y + SVG_SCALED(offY)), clip_.width(), clip_.height(), clip_); } bool TouchControls::FunctionLeft::isInside(int x, int y) { int dX = x - startX; int dY = y - startY; // norm 2 squared (to avoid square root) unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); if (sqNorm <= FUNC_SVG_SQ_SCALED(ARROW_END)) { return true; } // Also accept touches near the old last touch dX = x - currentX; dY = y - currentY; sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); if (sqNorm <= FUNC_SVG_SQ_SCALED(ARROW_DEAD)) { return true; } return false; } void TouchControls::FunctionLeft::maskToLeftButtons(uint32 oldMask, uint32 newMask) { static const Common::JoystickButton buttons[] = { Common::JOYSTICK_BUTTON_DPAD_UP, Common::JOYSTICK_BUTTON_DPAD_RIGHT, Common::JOYSTICK_BUTTON_DPAD_DOWN, Common::JOYSTICK_BUTTON_DPAD_LEFT }; uint32 diff = newMask ^ oldMask; for(int i = 0, m = 1; i < ARRAYSIZE(buttons); i++, m <<= 1) { if (!(diff & m)) { continue; } if (oldMask & m) { TouchControls::buttonUp(buttons[i]); } } if (diff & 16) { if (oldMask & 16) { TouchControls::buttonUp(Common::JOYSTICK_BUTTON_RIGHT_SHOULDER); } else { TouchControls::buttonDown(Common::JOYSTICK_BUTTON_RIGHT_SHOULDER); } } for(int i = 0, m = 1; i < ARRAYSIZE(buttons); i++, m <<= 1) { if (!(diff & m)) { continue; } if (newMask & m) { TouchControls::buttonDown(buttons[i]); } } } void TouchControls::FunctionLeft::touch(int dX, int dY, Action action) { if (action == JACTION_CANCEL || action == JACTION_UP) { maskToLeftButtons(mask, 0); resetState(); return; } uint32 newMask = 0; // norm 2 squared (to avoid square root) unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); if (sqNorm >= FUNC_SVG_SQ_SCALED(ARROW_DEAD)) { // We are far enough from the center // For right we use angles -60,60 as a sensitive zone // For left it's the same but mirrored in negative // We must be between the two lines which are of tan(60),tan(-60) and this corrsponds to sqrt(3) // For up down we use angles -30,30 as a sensitive zone // We must be outside the two lines which are of tan(30),tan(-30) and this corrsponds to 1/sqrt(3) /* static const double SQRT3 = 1.73205080756887719318; unsigned int sq3 = SQRT3 * abs(dX); */ // Optimize by using an approximation of sqrt(3) unsigned int sq3 = abs(dX) * 51409 / 29681; unsigned int isq3 = abs(dX) * 29681 / 51409; unsigned int adY = abs(dY); if (adY <= sq3) { // Left or right if (dX < 0) { newMask |= 8; } else { newMask |= 2; } } if (adY >= isq3) { // Up or down if (dY < 0) { newMask |= 1; } else { newMask |= 4; } } } if (sqNorm > FUNC_SVG_SQ_SCALED(ARROW_SUPER)) { newMask |= 16; } if (mask != newMask) { maskToLeftButtons(mask, newMask); } mask = newMask; } void TouchControls::FunctionLeft::draw(uint8 alpha) { // Draw background { Common::Rect clip(0, 0, BG_WIDTH, BG_HEIGHT); parent->drawSurface(alpha, startX, startY, -clip.width() / 2, -clip.height() / 2, clip); } if (mask == 0) { return; } // Width and height here are rotated for left/right uint16 width = ARROW_WIDTH; uint16 height; if (mask & 16) { height = ARROW_HEIGHT; } else { height = ARROW_SEMI_HEIGHT; } // We can draw multiple arrows if (mask & 1) { // Draw UP Common::Rect clip(width, height); clip.translate(0, ARROW_HL_TOP + ARROW_HEIGHT - height); int16 offX = -1, offY = -2; parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip); } if (mask & 2) { // Draw RIGHT Common::Rect clip(height, width); clip.translate(ARROW_WIDTH, ARROW_HL_TOP); int16 offX = 0, offY = -1; parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip); } if (mask & 4) { // Draw DOWN Common::Rect clip(width, height); clip.translate(0, ARROW_HL_TOP + ARROW_HEIGHT); int16 offX = -1, offY = 0; parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip); } if (mask & 8) { // Draw LEFT Common::Rect clip(height, width); clip.translate(ARROW_WIDTH + ARROW_WIDTH - height, ARROW_HL_TOP + ARROW_HEIGHT); int16 offX = -2, offY = -1; parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip); } } bool TouchControls::FunctionRight::isInside(int x, int y) { int dX = x - startX; int dY = y - startY; // norm 2 squared (to avoid square root) unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); return sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END); } void TouchControls::FunctionRight::touch(int dX, int dY, Action action) { static const Common::JoystickButton buttons[] = { Common::JOYSTICK_BUTTON_Y, Common::JOYSTICK_BUTTON_B, Common::JOYSTICK_BUTTON_A, Common::JOYSTICK_BUTTON_X }; static const Common::JoystickButton modifiers[] = { Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID }; if (action == JACTION_CANCEL || action == JACTION_UP) { if (button) { buttonUp(buttons[button - 1]); buttonUp(modifiers[button - 1]); } resetState(); return; } uint32 newButton = 0; // norm 2 squared (to avoid square root) unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); if (sqNorm >= FUNC_SVG_SQ_SCALED(BUTTON_DEAD) && sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END)) { // We are far enough from the center // For right we use angles -45,45 as a sensitive zone // For left it's the same but mirrored in negative // We must be between the two lines which are of tan(45),tan(-45) and this corrsponds to 1 // For up down we use angles -45,45 as a sensitive zone // We must be outside the two lines which are of tan(45),tan(-45) and this corrsponds to 1 unsigned int adX = abs(dX); unsigned int adY = abs(dY); if (adY <= adX) { // X or B if (dX < 0) { newButton = 4; } else { newButton = 2; } } else { // Y or A if (dY < 0) { newButton = 1; } else { newButton = 3; } } } else { newButton = 0; } if (button != newButton) { // Release the previously pressed button, if any if (button) { buttonUp(buttons[button - 1]); buttonUp(modifiers[button - 1]); } button = newButton; // Press the new button if (button) { buttonDown(modifiers[button - 1]); buttonDown(buttons[button - 1]); } } } void TouchControls::FunctionRight::draw(uint8 alpha) { // Draw background { Common::Rect clip(BG_WIDTH, 0, 2 * BG_WIDTH, BG_HEIGHT); parent->drawSurface(alpha, startX, startY, -clip.width() / 2, -clip.height() / 2, clip); } if (button == 0) { return; } Common::Rect clip(BUTTON_WIDTH, BUTTON_HEIGHT); int16 offX, offY; if (button == 1) { // Draw Y clip.translate(BUTTON_HL_LEFT, BUTTON_HL_TOP); offX = -1; offY = -2; } else if (button == 2) { // Draw B clip.translate(BUTTON_HL_LEFT + BUTTON_WIDTH, BUTTON_HL_TOP); offX = 0; offY = -1; } else if (button == 3) { // Draw A clip.translate(BUTTON_HL_LEFT, BUTTON_HL_TOP + BUTTON_HEIGHT); offX = -1; offY = 0; } else if (button == 4) { // Draw X clip.translate(BUTTON_HL_LEFT + BUTTON_WIDTH, BUTTON_HL_TOP + BUTTON_HEIGHT); offX = -2; offY = -1; } else { return; } parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip); } bool TouchControls::FunctionCenter::isInside(int x, int y) { int dX = x - startX; int dY = y - startY; // norm 2 squared (to avoid square root) unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); return sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END); } void TouchControls::FunctionCenter::touch(int dX, int dY, Action action) { static const Common::JoystickButton buttons[] = { Common::JOYSTICK_BUTTON_GUIDE, Common::JOYSTICK_BUTTON_RIGHT_STICK, Common::JOYSTICK_BUTTON_START, Common::JOYSTICK_BUTTON_LEFT_STICK }; static const Common::JoystickButton modifiers[] = { Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID }; if (action == JACTION_CANCEL || action == JACTION_UP) { if (button) { buttonUp(buttons[button - 1]); buttonUp(modifiers[button - 1]); } resetState(); return; } uint32 newButton = 0; // norm 2 squared (to avoid square root) unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY); if (sqNorm >= FUNC_SVG_SQ_SCALED(BUTTON_DEAD) && sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END)) { // We are far enough from the center // For right we use angles -45,45 as a sensitive zone // For left it's the same but mirrored in negative // We must be between the two lines which are of tan(45),tan(-45) and this corrsponds to 1 // For up down we use angles -45,45 as a sensitive zone // We must be outside the two lines which are of tan(45),tan(-45) and this corrsponds to 1 unsigned int adX = abs(dX); unsigned int adY = abs(dY); if (adY <= adX) { // X or B if (dX < 0) { newButton = 4; } else { newButton = 2; } } else { // Y or A if (dY < 0) { newButton = 1; } else { newButton = 3; } } } else { newButton = 0; } if (button != newButton) { // Release the previously pressed button, if any if (button) { buttonUp(buttons[button - 1]); buttonUp(modifiers[button - 1]); } button = newButton; // Press the new button if (button) { buttonDown(modifiers[button - 1]); buttonDown(buttons[button - 1]); } } } void TouchControls::FunctionCenter::draw(uint8 alpha) { // Draw background { Common::Rect clip(BG_WIDTH * 2, 0, 3 * BG_WIDTH, BG_HEIGHT); parent->drawSurface(alpha, startX, startY, -clip.width() / 2, -clip.height() / 2, clip); } if (button == 0) { return; } Common::Rect clip(BUTTON_WIDTH, BUTTON_HEIGHT); int16 offX, offY; if (button == 1) { // Draw Y clip.translate(BUTTON2_HL_LEFT, BUTTON2_HL_TOP); offX = -1; offY = -2; } else if (button == 2) { // Draw B clip.translate(BUTTON2_HL_LEFT + BUTTON_WIDTH, BUTTON2_HL_TOP); offX = 0; offY = -1; } else if (button == 3) { // Draw A clip.translate(BUTTON2_HL_LEFT, BUTTON2_HL_TOP + BUTTON_HEIGHT); offX = -1; offY = 0; } else if (button == 4) { // Draw X clip.translate(BUTTON2_HL_LEFT + BUTTON_WIDTH, BUTTON2_HL_TOP + BUTTON_HEIGHT); offX = -2; offY = -1; } else { return; } // Always draw overlay as opaque parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip); }