ANDROID: Introduce back the touch controls for 3D

This commit is contained in:
Le Philousophe 2021-10-09 21:23:23 +02:00 committed by Paweł Kołodziejski
parent ebeb731ad7
commit e4390abd86
13 changed files with 541 additions and 353 deletions

View File

@ -76,6 +76,7 @@ AndroidGraphics3dManager::AndroidGraphics3dManager() :
_mouse_texture = _mouse_texture_palette;
initSurface();
JNI::setTouch3DMode(true);
}
AndroidGraphics3dManager::~AndroidGraphics3dManager() {
@ -94,6 +95,7 @@ AndroidGraphics3dManager::~AndroidGraphics3dManager() {
delete _mouse_texture_palette;
delete _mouse_texture_rgb;
JNI::setTouch3DMode(false);
}
static void logExtensions() {
@ -150,7 +152,7 @@ void AndroidGraphics3dManager::initSurface() {
clearScreen(kClearUpdate, 2);
updateEventScale();
_touchControls.init(JNI::egl_surface_width, JNI::egl_surface_height);
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().init(JNI::egl_surface_width, JNI::egl_surface_height);
}
void AndroidGraphics3dManager::deinitSurface() {
@ -212,7 +214,7 @@ void AndroidGraphics3dManager::updateScreen() {
_game_texture->drawTextureRect();
if (!_show_overlay) {
glEnable(GL_BLEND);
_touchControls.draw();
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().draw();
}
int cs = _mouse_targetscale;
@ -316,6 +318,8 @@ bool AndroidGraphics3dManager::getFeatureState(OSystem::Feature f) const {
void AndroidGraphics3dManager::showOverlay() {
ENTER();
JNI::setTouch3DMode(false);
_show_overlay = true;
_force_redraw = true;
@ -331,6 +335,8 @@ void AndroidGraphics3dManager::hideOverlay() {
_show_overlay = false;
JNI::setTouch3DMode(true);
updateEventScale();
warpMouse(_game_texture->width() / 2, _game_texture->height() / 2);

View File

@ -29,7 +29,6 @@
#include "backends/graphics/graphics.h"
#include "backends/graphics/android/android-graphics.h"
#include "backends/graphics3d/android/texture.h"
#include "backends/graphics3d/android/touchcontrols.h"
class AndroidGraphics3dManager : public GraphicsManager, public AndroidCommonGraphics {
public:
@ -175,8 +174,6 @@ private:
int _mouse_targetscale;
bool _show_mouse;
bool _use_mouse_palette;
TouchControls _touchControls;
};
#endif

View File

@ -1,314 +0,0 @@
/* ResidualVM - A 3D game interpreter
*
* ResidualVM 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h>
#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 "common/fs.h"
#include "common/stream.h"
#include "common/archive.h"
#include "image/tga.h"
#include "backends/graphics3d/android/texture.h"
#include "backends/graphics3d/android/touchcontrols.h"
#include "backends/platform/android/android.h"
// action type
enum {
JACTION_DOWN = 0,
JACTION_UP = 1,
JACTION_MULTIPLE = 2,
JACTION_POINTER_DOWN = 5,
JACTION_POINTER_UP = 6
};
static Common::Rect clipFor(const Common::KeyCode &cs) {
switch (cs) {
case Common::KEYCODE_UP:
case Common::KEYCODE_PAGEUP:
return Common::Rect(0, 0, 128, 128);
case Common::KEYCODE_RIGHT:
return Common::Rect(128, 0, 256, 128);
case Common::KEYCODE_DOWN:
case Common::KEYCODE_PAGEDOWN:
return Common::Rect(256, 0, 384, 128);
case Common::KEYCODE_LEFT:
return Common::Rect(384, 0, 512, 128);
case Common::KEYCODE_i:
return Common::Rect(0, 128, 128, 256);
case Common::KEYCODE_p:
return Common::Rect(128, 128, 256, 256);
case Common::KEYCODE_u:
return Common::Rect(256, 128, 384, 256);
case Common::KEYCODE_e:
case Common::KEYCODE_l:
return Common::Rect(384, 128, 512, 256);
default: // unreachable
return Common::Rect(0, 0, 1, 1);
}
}
TouchControls::TouchControls() :
_arrows_texture(NULL),
_joystickPressing(Common::KEYCODE_INVALID),
_centerPressing(Common::KEYCODE_INVALID),
_rightPressing(Common::KEYCODE_INVALID),
_screen_width(0),
_screen_height(0) {
for (int p = 0; p < kNumPointers; ++p) {
Pointer &pp = _pointers[p];
pp.currentX = pp.currentY = pp.startX = pp.startY = 0;
pp.active = false;
pp.function = kTouchAreaNone;
}
for (int i = 0; i < 4; ++i)
_activePointers[i] = -1;
}
TouchControls::~TouchControls() {
if (_arrows_texture) {
delete _arrows_texture;
_arrows_texture = 0;
}
}
uint16 TouchControls::getTouchArea(int x, int y) {
float xPercent = float(x) / _screen_width;
if (xPercent < 0.3)
return kTouchAreaJoystick;
else if (xPercent < 0.8)
return kTouchAreaCenter;
else
return kTouchAreaRight;
}
static Common::KeyCode determineKey(int dX, int dY, Common::KeyCode def = Common::KEYCODE_INVALID) {
if (dX * dX + dY * dY < 50 * 50)
return def;
if (dY > abs(dX))
return Common::KEYCODE_DOWN;
if (dX > abs(dY))
return Common::KEYCODE_RIGHT;
if (-dY > abs(dX))
return Common::KEYCODE_UP;
if (-dX > abs(dY))
return Common::KEYCODE_LEFT;
return Common::KEYCODE_INVALID;
}
static GLES8888Texture *loadBuiltinTexture(const char *filename) {
Common::ArchiveMemberPtr member = SearchMan.getMember(filename);
Common::SeekableReadStream *str = member->createReadStream();
Image::TGADecoder dec;
dec.loadStream(*str);
const void *pixels = dec.getSurface()->getPixels();
GLES8888Texture *ret = new GLES8888Texture();
uint16 w = dec.getSurface()->w;
uint16 h = dec.getSurface()->h;
uint16 pitch = dec.getSurface()->pitch;
ret->allocBuffer(w, h);
ret->updateBuffer(0, 0, w, h, pixels, pitch);
delete str;
return ret;
}
void TouchControls::init(int width, int height) {
_arrows_texture = loadBuiltinTexture("arrows.tga");
_screen_width = width;
_screen_height = height;
}
const uint _numRightKeycodes = 4;
const Common::KeyCode _rightKeycodes[] = { Common::KEYCODE_i, Common::KEYCODE_p, Common::KEYCODE_u, Common::KEYCODE_e };
void TouchControls::draw() {
if (_joystickPressing != Common::KEYCODE_INVALID) {
Common::Rect clip = clipFor(_joystickPressing);
_arrows_texture->drawTexture(2 * _screen_width / 10, _screen_height / 2, 64, 64, clip);
}
if (_centerPressing != Common::KEYCODE_INVALID) {
Common::Rect clip = clipFor(_centerPressing);
_arrows_texture->drawTexture(_screen_width / 2, _screen_height / 2, 64, 64, clip);
}
if (_rightPressing != Common::KEYCODE_INVALID) {
Common::Rect clip = clipFor(_rightPressing);
_arrows_texture->drawTexture( 8 * _screen_width / 10, _screen_height / 2, 64, 64, clip);
}
}
void TouchControls::update(int ptr, int action, int x, int y) {
if (ptr > kNumPointers)
return;
TouchArea touchArea = (TouchArea) getTouchArea(x, y);
switch (action) {
case JACTION_POINTER_DOWN:
case JACTION_DOWN:
if (touchArea > kTouchAreaNone && -1 == pointerFor(touchArea)) {
pointerFor(touchArea) = ptr;
_pointers[ptr].active = true;
_pointers[ptr].function = touchArea;
_pointers[ptr].startX = _pointers[ptr].currentX = x;
_pointers[ptr].startY = _pointers[ptr].currentY = y;
// fall through to move case to initialize _{joy,center,right}Pressing
} else {
return;
}
case JACTION_MULTIPLE: {
_pointers[ptr].currentX = x;
_pointers[ptr].currentY = y;
int dX = x - _pointers[ptr].startX;
int dY = y - _pointers[ptr].startY;
switch (_pointers[ptr].function) {
case kTouchAreaJoystick: {
Common::KeyCode newPressing = determineKey(dX, dY);
if (newPressing != _joystickPressing) {
keyUp(_joystickPressing);
keyDown(newPressing);
_joystickPressing = newPressing;
} else if(abs(dY) > 150) {
keyDown(Common::KEYCODE_LSHIFT);
} else if(abs(dY) <= 150){
keyUp(Common::KEYCODE_LSHIFT);
}
return;
}
case kTouchAreaCenter:
_centerPressing = determineKey(dX, dY, Common::KEYCODE_RETURN);
return;
case kTouchAreaRight:
_rightPressing = determineKey(dX, dY, Common::KEYCODE_i);
switch (_rightPressing) {
case Common::KEYCODE_LEFT:
case Common::KEYCODE_RIGHT:
_rightPressing = _rightKeycodes[abs(dX / 100) % _numRightKeycodes];
break;
case Common::KEYCODE_UP:
_rightPressing = Common::KEYCODE_PAGEUP;
break;
case Common::KEYCODE_DOWN:
_rightPressing = Common::KEYCODE_PAGEDOWN;
break;
default:
break;
}
default:
return;
}
return;
}
case JACTION_UP:
case JACTION_POINTER_UP: {
switch (_pointers[ptr].function) {
case kTouchAreaJoystick:
pointerFor(kTouchAreaJoystick) = -1;
if (_joystickPressing != Common::KEYCODE_INVALID) {
keyUp(_joystickPressing);
_joystickPressing = Common::KEYCODE_INVALID;
keyUp(Common::KEYCODE_LSHIFT);
}
break;
case kTouchAreaCenter:
pointerFor(kTouchAreaCenter) = -1;
keyPress(_centerPressing);
_centerPressing = Common::KEYCODE_INVALID;
break;
case kTouchAreaRight:
pointerFor(kTouchAreaRight) = -1;
keyPress(_rightPressing);
_rightPressing = Common::KEYCODE_INVALID;
break;
case kTouchAreaNone:
default:
break;
}
_pointers[ptr].active = false;
_pointers[ptr].function = kTouchAreaNone;
return;
}
}
}
int &TouchControls::pointerFor(TouchArea ta) {
return _activePointers[ta - kTouchAreaNone];
}
void TouchControls::keyDown(Common::KeyCode kc) {
Common::Event ev;
ev.type = Common::EVENT_KEYDOWN;
ev.kbd.keycode = kc;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev);
}
void TouchControls::keyUp(Common::KeyCode kc) {
Common::Event ev;
ev.type = Common::EVENT_KEYUP;
ev.kbd.keycode = kc;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev);
}
void TouchControls::keyPress(Common::KeyCode kc) {
Common::Event ev;
ev.kbd.keycode = kc;
dynamic_cast<OSystem_Android *>(g_system)->pushKeyPressEvent(ev);
}
#endif

View File

@ -235,7 +235,6 @@ MODULE_OBJS += \
graphics/android/android-graphics.o \
graphics3d/android/android-graphics3d.o \
graphics3d/android/texture.o \
graphics3d/android/touchcontrols.o \
mutex/pthread/pthread-mutex.o
endif

View File

@ -35,6 +35,8 @@
#include "backends/plugins/posix/posix-provider.h"
#include "backends/fs/posix/posix-fs-factory.h"
#include "backends/platform/android/touchcontrols.h"
#include <pthread.h>
#include <android/log.h>
@ -132,6 +134,8 @@ public:
void pushEvent(const Common::Event &event);
void pushKeyPressEvent(Common::Event &event);
TouchControls &getTouchControls() { return _touchControls; }
private:
Common::Queue<Common::Event> _event_queue;
Common::Event _queuedEvent;
@ -151,6 +155,8 @@ private:
int _secondPointerId;
int _thirdPointerId;
TouchControls _touchControls;
public:
bool pollEvent(Common::Event &event) override;
Common::HardwareInputSet *getHardwareInputSet() override;

View File

@ -84,6 +84,7 @@ jmethodID JNI::_MID_isConnectionLimited = 0;
jmethodID JNI::_MID_setWindowCaption = 0;
jmethodID JNI::_MID_showVirtualKeyboard = 0;
jmethodID JNI::_MID_showKeyboardControl = 0;
jmethodID JNI::_MID_setTouch3DMode = 0;
jmethodID JNI::_MID_showSAFRevokePermsControl = 0;
jmethodID JNI::_MID_getSysArchives = 0;
jmethodID JNI::_MID_getAllStorageLocations = 0;
@ -118,6 +119,8 @@ const JNINativeMethod JNI::_natives[] = {
(void *)JNI::main },
{ "pushEvent", "(IIIIIII)V",
(void *)JNI::pushEvent },
{ "updateTouch", "(IIII)V",
(void *)JNI::updateTouch },
{ "setPause", "(Z)V",
(void *)JNI::setPause },
{ "getNativeVersionInfo", "()Ljava/lang/String;",
@ -386,6 +389,19 @@ void JNI::showKeyboardControl(bool enable) {
}
}
void JNI::setTouch3DMode(bool touch3DMode) {
JNIEnv *env = JNI::getEnv();
env->CallVoidMethod(_jobj, _MID_setTouch3DMode, touch3DMode);
if (env->ExceptionCheck()) {
LOGE("Error trying to show virtual keyboard control");
env->ExceptionDescribe();
env->ExceptionClear();
}
}
void JNI::showSAFRevokePermsControl(bool enable) {
#ifndef BACKEND_ANDROID3D
JNIEnv *env = JNI::getEnv();
@ -571,6 +587,7 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
FIND_METHOD(, isConnectionLimited, "()Z");
FIND_METHOD(, showVirtualKeyboard, "(Z)V");
FIND_METHOD(, showKeyboardControl, "(Z)V");
FIND_METHOD(, setTouch3DMode, "(Z)V");
FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;");
FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
@ -713,6 +730,18 @@ void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2,
_system->pushEvent(type, arg1, arg2, arg3, arg4, arg5, arg6);
}
void JNI::updateTouch(JNIEnv *env, jobject self, int action, int ptr, int x, int y) {
// drop events until we're ready and after we quit
if (!_ready_for_events) {
LOGW("dropping event");
return;
}
assert(_system);
_system->getTouchControls().update((TouchControls::Action) action, ptr, x, y);
}
void JNI::setPause(JNIEnv *env, jobject self, jboolean value) {
if (!_system)
return;

View File

@ -68,6 +68,7 @@ public:
static bool isConnectionLimited();
static void showVirtualKeyboard(bool enable);
static void showKeyboardControl(bool enable);
static void setTouch3DMode(bool touch3DMode);
static void showSAFRevokePermsControl(bool enable);
static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority);
@ -114,6 +115,7 @@ private:
static jmethodID _MID_setWindowCaption;
static jmethodID _MID_showVirtualKeyboard;
static jmethodID _MID_showKeyboardControl;
static jmethodID _MID_setTouch3DMode;
static jmethodID _MID_showSAFRevokePermsControl;
static jmethodID _MID_getSysArchives;
static jmethodID _MID_getAllStorageLocations;
@ -149,6 +151,7 @@ private:
static void pushEvent(JNIEnv *env, jobject self, int type, int arg1,
int arg2, int arg3, int arg4, int arg5, int arg6);
static void updateTouch(JNIEnv *env, jobject self, int action, int ptr, int x, int y);
static void setPause(JNIEnv *env, jobject self, jboolean value);
static jstring getNativeVersionInfo(JNIEnv *env, jobject self);

View File

@ -6,7 +6,8 @@ MODULE_OBJS := \
android.o \
events.o \
options.o \
snprintf.o
snprintf.o \
touchcontrols.o
# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS.
MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS))

View File

@ -54,6 +54,8 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
// Feed an event to ScummVM. Safe to call from other threads.
final public native void pushEvent(int type, int arg1, int arg2, int arg3,
int arg4, int arg5, int arg6);
// Update the 3D touch controls
final public native void updateTouch(int action, int ptr, int x, int y);
final public native String getNativeVersionInfo();
@ -68,6 +70,7 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
abstract protected void setWindowCaption(String caption);
abstract protected void showVirtualKeyboard(boolean enable);
abstract protected void showKeyboardControl(boolean enable);
abstract protected void setTouch3DMode(boolean touch3DMode);
abstract protected void showSAFRevokePermsControl(boolean enable);
abstract protected String[] getSysArchives();
abstract protected String[] getAllStorageLocations();

View File

@ -699,6 +699,15 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
});
}
@Override
protected void setTouch3DMode(final boolean touch3DMode) {
runOnUiThread(new Runnable() {
public void run() {
_events.setTouch3DMode(touch3DMode);
}
});
}
@Override
protected void showSAFRevokePermsControl(final boolean enable) {
runOnUiThread(new Runnable() {

View File

@ -50,6 +50,11 @@ public class ScummVMEventsBase implements
public static final int JE_QUIT = 0x1000;
public static final int JE_MENU = 0x1001;
public static final int JACTION_DOWN = 0;
public static final int JACTION_MOVE = 1;
public static final int JACTION_UP = 2;
public static final int JACTION_CANCEL = 3;
final protected Context _context;
final protected ScummVM _scummvm;
final protected GestureDetector _gd;
@ -57,6 +62,8 @@ public class ScummVMEventsBase implements
final protected MouseHelper _mouseHelper;
final protected MultitouchHelper _multitouchHelper;
protected boolean _touch3DMode;
// Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occur”) based on:
// https://stackoverflow.com/a/27826094
public static class ScummVMEventHandler extends Handler {
@ -135,6 +142,13 @@ public class ScummVMEventsBase implements
}
}
final public void setTouch3DMode(boolean touch3DMode) {
if (_touch3DMode != touch3DMode && !touch3DMode) {
_scummvm.updateTouch(JACTION_CANCEL, 0, 0, 0);
}
_touch3DMode = touch3DMode;
}
public void clearEventHandler() {
_skeyHandler.clear();
_multitouchHelper.clearEventHandler();
@ -459,30 +473,60 @@ public class ScummVMEventsBase implements
}
}
// Deal with LINT warning "ScummVMEvents#onTouch should call View#performClick when a click is detected"
switch (action) {
case MotionEvent.ACTION_UP:
v.performClick();
break;
case MotionEvent.ACTION_DOWN:
// fall through
default:
break;
}
if (_touch3DMode) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
int idx = event.getActionIndex();
_scummvm.updateTouch(JACTION_DOWN, event.getPointerId(idx), (int)event.getX(idx), (int)event.getY(idx));
// fall through
}
case MotionEvent.ACTION_MOVE:
for(int idx = 0; idx < event.getPointerCount(); idx++) {
_scummvm.updateTouch(JACTION_MOVE, event.getPointerId(idx), (int)event.getX(idx), (int)event.getY(idx));
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: {
int idx = event.getActionIndex();
_scummvm.updateTouch(JACTION_UP, event.getPointerId(idx), (int)event.getX(idx), (int)event.getY(idx));
break;
}
case MotionEvent.ACTION_CANCEL:
_scummvm.updateTouch(JACTION_CANCEL, 0, 0, 0);
break;
}
// check if the event can be handled as a multitouch event
if (_multitouchHelper.handleMotionEvent(event)) {
//return _gd.onTouchEvent(event);
return true;
}
} else {
// Deal with LINT warning "ScummVMEvents#onTouch should call View#performClick when a click is detected"
switch (action) {
case MotionEvent.ACTION_UP:
v.performClick();
break;
case MotionEvent.ACTION_DOWN:
// fall through
default:
break;
}
return _gd.onTouchEvent(event);
// check if the event can be handled as a multitouch event
if (_multitouchHelper.handleMotionEvent(event)) {
return true;
}
return _gd.onTouchEvent(event);
}
}
// OnGestureListener
@Override
final public boolean onDown(MotionEvent e) {
// Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onDOWN MotionEvent");
_scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0, 0);
if (!_touch3DMode) {
_scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0, 0);
}
return true;
}
@ -505,9 +549,10 @@ public class ScummVMEventsBase implements
final public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// Log.d(ScummVM.LOG_TAG, "onScroll");
_scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(),
if (!_touch3DMode) {
_scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(),
(int)e2.getX(), (int)e2.getY(), 0, 0);
}
return true;
}
@ -518,9 +563,10 @@ public class ScummVMEventsBase implements
@Override
final public boolean onSingleTapUp(MotionEvent e) {
// Log.d(ScummVM.LOG_TAG, "onSingleTapUp");
_scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(),
if (!_touch3DMode) {
_scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(),
(int)(e.getEventTime() - e.getDownTime()), 0, 0, 0);
}
return true;
}
@ -545,7 +591,9 @@ public class ScummVMEventsBase implements
// } else {
// Log.d(ScummVM.LOG_TAG, "onDoubleTapEvent UNKNOWN!!!!");
// }
_scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(), e.getAction(), 0, 0, 0);
if (!_touch3DMode) {
_scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(), e.getAction(), 0, 0, 0);
}
return true;
}

View File

@ -0,0 +1,362 @@
/* ResidualVM - A 3D game interpreter
*
* ResidualVM 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h>
#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 "common/fs.h"
#include "common/stream.h"
#include "common/archive.h"
#include "image/tga.h"
#include "backends/graphics3d/android/texture.h"
#include "backends/platform/android/android.h"
#include "backends/platform/android/touchcontrols.h"
TouchControls::TouchControls() :
_arrows_texture(NULL),
_screen_width(0),
_screen_height(0) {
}
TouchControls::~TouchControls() {
if (_arrows_texture) {
delete _arrows_texture;
_arrows_texture = 0;
}
}
TouchControls::Function TouchControls::getFunction(int x, int y) {
float xPercent = float(x) / _screen_width;
if (xPercent < 0.3)
return kFunctionJoystick;
else if (xPercent < 0.8)
return kFunctionCenter;
else
return kFunctionRight;
}
void TouchControls::touchToJoystickState(int dX, int dY, FunctionState &state) {
int sqNorm = dX * dX + dY * dY;
if (sqNorm < 50 * 50) {
return;
}
if (dY > abs(dX)) {
state.main = Common::KEYCODE_DOWN;
state.clip = Common::Rect(256, 0, 384, 128);
} else if (dX > abs(dY)) {
state.main = Common::KEYCODE_RIGHT;
state.clip = Common::Rect(128, 0, 256, 128);
} else if (-dY > abs(dX)) {
state.main = Common::KEYCODE_UP;
state.clip = Common::Rect(0, 0, 128, 128);
} else if (-dX > abs(dY)) {
state.main = Common::KEYCODE_LEFT;
state.clip = Common::Rect(384, 0, 512, 128);
} else {
return;
}
if (sqNorm > 20000) {
state.modifier = Common::KEYCODE_LSHIFT;
}
}
void TouchControls::touchToCenterState(int dX, int dY, FunctionState &state) {
int sqNorm = dX * dX + dY * dY;
if (sqNorm < 50 * 50) {
state.main = Common::KEYCODE_RETURN;
}
}
void TouchControls::touchToRightState(int dX, int dY, FunctionState &state) {
if (dX * dX + dY * dY < 100 * 100)
return;
if (dY > abs(dX)) {
// down
state.main = Common::KEYCODE_PAGEDOWN;
state.clip = Common::Rect(256, 0, 384, 128);
return;
} else if (-dY > abs(dX)) {
// up
state.main = Common::KEYCODE_PAGEUP;
state.clip = Common::Rect(0, 0, 128, 128);
return;
}
static Common::KeyCode keycodes[5] = {
Common::KEYCODE_i, Common::KEYCODE_p, // left zone
Common::KEYCODE_INVALID, // center
Common::KEYCODE_u, Common::KEYCODE_l // right zone
};
static int16 clips[5][4] = {
{ 0, 128, 128, 256 }, // i
{ 128, 128, 256, 256 }, // p
{ 0, 0, 0, 0 }, // center
{ 256, 128, 384, 256 }, // u
{ 384, 128, 512, 256 } // l
};
static const unsigned int offset = (ARRAYSIZE(keycodes) - 1) / 2;
int idx = (dX / 100) + offset;
if (idx < 0) {
idx = 0;
}
if (idx >= ARRAYSIZE(keycodes)) {
idx = ARRAYSIZE(keycodes) - 1;
}
state.main = keycodes[idx];
state.clip = Common::Rect(clips[idx][0], clips[idx][1], clips[idx][2], clips[idx][3]);
}
TouchControls::FunctionBehavior TouchControls::functionBehaviors[TouchControls::kFunctionMax+1] = {
{ touchToJoystickState, false, .2f, .5f },
{ touchToCenterState, true, .5f, .5f },
{ touchToRightState, true, .8f, .5f }
};
static GLES8888Texture *loadBuiltinTexture(const char *filename) {
Common::ArchiveMemberPtr member = SearchMan.getMember(filename);
Common::SeekableReadStream *str = member->createReadStream();
Image::TGADecoder dec;
dec.loadStream(*str);
const void *pixels = dec.getSurface()->getPixels();
GLES8888Texture *ret = new GLES8888Texture();
uint16 w = dec.getSurface()->w;
uint16 h = dec.getSurface()->h;
uint16 pitch = dec.getSurface()->pitch;
ret->allocBuffer(w, h);
ret->updateBuffer(0, 0, w, h, pixels, pitch);
delete str;
return ret;
}
void TouchControls::init(int width, int height) {
_arrows_texture = loadBuiltinTexture("arrows.tga");
_screen_width = width;
_screen_height = height;
}
TouchControls::Pointer *TouchControls::getPointerFromId(int ptrId, bool createNotFound) {
unsigned int freeEntry = -1;
for (unsigned int i = 0; i < kNumPointers; i++) {
Pointer &ptr = _pointers[i];
if (ptr.active && (ptr.id == ptrId)) {
return &ptr;
}
if (createNotFound && (freeEntry == -1) && !ptr.active) {
freeEntry = i;
}
}
// Too much fingers or not found
if (freeEntry == -1) {
return nullptr;
}
Pointer &ptr = _pointers[freeEntry];
ptr.reset();
ptr.id = ptrId;
return &ptr;
}
TouchControls::Pointer *TouchControls::findPointerFromFunction(Function function) {
for (unsigned int i = 0; i < kNumPointers; i++) {
Pointer &ptr = _pointers[i];
if (ptr.active && (ptr.function == function)) {
return &ptr;
}
}
return nullptr;
}
void TouchControls::draw() {
for (unsigned int i = 0; i < kFunctionMax+1; i++) {
FunctionState &state = _functionStates[i];
FunctionBehavior behavior = functionBehaviors[i];
if (state.clip.isEmpty()) {
continue;
}
_arrows_texture->drawTexture(_screen_width * behavior.xRatio, _screen_height * behavior.yRatio, 64, 64, state.clip);
}
}
void TouchControls::update(Action action, int ptrId, int x, int y) {
if (action == JACTION_DOWN) {
Pointer *ptr = getPointerFromId(ptrId, true);
if (!ptr) {
return;
}
TouchControls::Function function = getFunction(x, y);
// ptrId is active no matter what
ptr->active = true;
if (findPointerFromFunction(function)) {
// Some finger is already using this function: don't do anything
return;
}
ptr->startX = ptr->currentX = x;
ptr->startY = ptr->currentY = y;
ptr->function = function;
} else if (action == JACTION_MOVE) {
Pointer *ptr = getPointerFromId(ptrId, false);
if (!ptr || ptr->function == kFunctionNone) {
return;
}
FunctionBehavior &behavior = functionBehaviors[ptr->function];
ptr->currentX = x;
ptr->currentY = y;
int dX = x - ptr->startX;
int dY = y - ptr->startY;
FunctionState newState;
functionBehaviors[ptr->function].touchToState(dX, dY, newState);
FunctionState &oldState = _functionStates[ptr->function];
if (!behavior.keyPressOnRelease) {
// send key presses continuously
// first old remove main key, then update modifier, then press new main key
if (oldState.main != newState.main) {
keyUp(oldState.main);
}
if (oldState.modifier != newState.modifier) {
keyUp(oldState.modifier);
keyDown(newState.modifier);
}
if (oldState.main != newState.main) {
keyDown(newState.main);
}
}
oldState = newState;
} else if (action == JACTION_UP) {
Pointer *ptr = getPointerFromId(ptrId, false);
if (!ptr || ptr->function == kFunctionNone) {
return;
}
FunctionBehavior &behavior = functionBehaviors[ptr->function];
FunctionState &functionState = _functionStates[ptr->function];
if (!behavior.keyPressOnRelease) {
// We sent key down continously: keyUp everything
keyUp(functionState.main);
keyUp(functionState.modifier);
} else {
int dX = x - ptr->startX;
int dY = y - ptr->startY;
FunctionState newState;
functionBehaviors[ptr->function].touchToState(dX, dY, newState);
keyDown(newState.modifier);
keyPress(newState.main);
keyUp(newState.modifier);
}
functionState.reset();
ptr->active = false;
} else if (action == JACTION_CANCEL) {
for (unsigned int i = 0; i < kNumPointers; i++) {
Pointer &ptr = _pointers[i];
ptr.reset();
}
for (unsigned int i = 0; i < kFunctionMax+1; i++) {
FunctionBehavior &behavior = functionBehaviors[i];
FunctionState &functionState = _functionStates[i];
if (!behavior.keyPressOnRelease) {
// We sent key down continously: keyUp everything
keyUp(functionState.main);
keyUp(functionState.modifier);
}
functionState.reset();
}
}
}
void TouchControls::keyDown(Common::KeyCode kc) {
if (kc == Common::KEYCODE_INVALID) {
return;
}
Common::Event ev;
ev.type = Common::EVENT_KEYDOWN;
ev.kbd.keycode = kc;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev);
}
void TouchControls::keyUp(Common::KeyCode kc) {
if (kc == Common::KEYCODE_INVALID) {
return;
}
Common::Event ev;
ev.type = Common::EVENT_KEYUP;
ev.kbd.keycode = kc;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev);
}
void TouchControls::keyPress(Common::KeyCode kc) {
if (kc == Common::KEYCODE_INVALID) {
return;
}
Common::Event ev;
ev.kbd.keycode = kc;
dynamic_cast<OSystem_Android *>(g_system)->pushKeyPressEvent(ev);
}
#endif

View File

@ -31,41 +31,80 @@
class TouchControls {
public:
// action type
enum Action {
JACTION_DOWN = 0,
JACTION_MOVE = 1,
JACTION_UP = 2,
JACTION_CANCEL = 3
};
TouchControls();
~TouchControls();
void init(int width, int height);
void draw();
void update(int ptr, int action, int x, int y);
void update(Action action, int ptr, int x, int y);
private:
int _screen_width, _screen_height;
enum TouchArea{
kTouchAreaJoystick = 0xffff,
kTouchAreaCenter = 0xfffe,
kTouchAreaRight = 0xfffd,
kTouchAreaNone = 0xfffc,
enum Function {
kFunctionNone = -1,
kFunctionJoystick = 0,
kFunctionCenter = 1,
kFunctionRight = 2,
kFunctionMax = 2
};
uint16 getTouchArea(int x, int y);
Function getFunction(int x, int y);
struct Pointer {
Pointer() : id(-1), startX(-1), startY(-1), currentX(-1), currentY(-1), function(kFunctionNone), active(false) {}
void reset() { id = -1; startX = startY = currentX = currentY = -1; function = kFunctionNone; active = false; }
int id;
uint16 startX, startY;
uint16 currentX, currentY;
TouchArea function;
Function function;
bool active;
};
enum { kNumPointers = 5 };
Pointer _pointers[kNumPointers];
int _activePointers[4];
Common::KeyCode _joystickPressing, _centerPressing, _rightPressing;
int &pointerFor(TouchArea ta);
Pointer *getPointerFromId(int ptr, bool createNotFound);
Pointer *findPointerFromFunction(Function function);
struct FunctionState {
FunctionState() : main(Common::KEYCODE_INVALID), modifier(Common::KEYCODE_INVALID) { }
void reset() { main = Common::KEYCODE_INVALID; modifier = Common::KEYCODE_INVALID; clip = Common::Rect(); }
Common::KeyCode main;
Common::KeyCode modifier;
Common::Rect clip;
};
FunctionState _functionStates[kFunctionMax + 1];
GLESTexture *_arrows_texture;
void keyDown(Common::KeyCode kc);
void keyUp(Common::KeyCode kc);
void keyPress(Common::KeyCode kc);
/* Functions implementations */
struct FunctionBehavior {
void (*touchToState)(int, int, TouchControls::FunctionState &);
bool keyPressOnRelease;
float xRatio;
float yRatio;
};
static FunctionBehavior functionBehaviors[TouchControls::kFunctionMax+1];
static void touchToJoystickState(int dX, int dY, FunctionState &state);
static void touchToCenterState(int dX, int dY, FunctionState &state);
static void touchToRightState(int dX, int dY, FunctionState &state);
};
#endif