diff --git a/backends/graphics/android/android-graphics.cpp b/backends/graphics/android/android-graphics.cpp index f65e963bcd3..e0804d43ae2 100644 --- a/backends/graphics/android/android-graphics.cpp +++ b/backends/graphics/android/android-graphics.cpp @@ -42,24 +42,7 @@ #include "backends/graphics/opengl/pipelines/pipeline.h" #include "graphics/blit.h" - -static void loadBuiltinTexture(JNI::BitmapResources resource, OpenGL::Surface *surf) { - const Graphics::Surface *src = JNI::getBitmapResource(resource); - if (!src) { - error("Failed to fetch touch arrows bitmap"); - } - - surf->allocate(src->w, src->h); - Graphics::Surface *dst = surf->getSurface(); - - Graphics::crossBlit( - (byte *)dst->getPixels(), (const byte *)src->getPixels(), - dst->pitch, src->pitch, - src->w, src->h, - src->format, dst->format); - - delete src; -} +#include "graphics/managed_surface.h" // // AndroidGraphicsManager @@ -72,10 +55,6 @@ AndroidGraphicsManager::AndroidGraphicsManager() : // Initialize our OpenGL ES context. initSurface(); - _touchcontrols = createSurface(_defaultFormatAlpha); - loadBuiltinTexture(JNI::BitmapResources::TOUCH_ARROWS_BITMAP, _touchcontrols); - _touchcontrols->updateGLTexture(); - // not in 3D, not in GUI dynamic_cast(g_system)->applyTouchSettings(false, false); dynamic_cast(g_system)->applyOrientationSettings(); @@ -85,6 +64,8 @@ AndroidGraphicsManager::~AndroidGraphicsManager() { ENTER(); deinitSurface(); + + delete _touchcontrols; } void AndroidGraphicsManager::initSurface() { @@ -118,8 +99,10 @@ void AndroidGraphicsManager::initSurface() { if (_touchcontrols) { _touchcontrols->recreate(); _touchcontrols->updateGLTexture(); + } else { + _touchcontrols = createSurface(_defaultFormatAlpha); } - dynamic_cast(g_system)->getTouchControls().init( + dynamic_cast(g_system)->getTouchControls().setDrawer( this, JNI::egl_surface_width, JNI::egl_surface_height); handleResize(JNI::egl_surface_width, JNI::egl_surface_height); @@ -132,7 +115,7 @@ void AndroidGraphicsManager::deinitSurface() { LOGD("deinitializing 2D surface"); // Deregister us from touch control - dynamic_cast(g_system)->getTouchControls().init( + dynamic_cast(g_system)->getTouchControls().setDrawer( nullptr, 0, 0); if (_touchcontrols) { _touchcontrols->destroy(); @@ -157,7 +140,7 @@ void AndroidGraphicsManager::resizeSurface() { error("JNI::initSurface failed"); } - dynamic_cast(g_system)->getTouchControls().init( + dynamic_cast(g_system)->getTouchControls().setDrawer( this, JNI::egl_surface_width, JNI::egl_surface_height); handleResize(JNI::egl_surface_width, JNI::egl_surface_height); @@ -170,6 +153,9 @@ void AndroidGraphicsManager::updateScreen() { if (!JNI::haveSurface()) return; + // Sets _forceRedraw if needed + dynamic_cast(g_system)->getTouchControls().beforeDraw(); + OpenGLGraphicsManager::updateScreen(); } @@ -246,12 +232,34 @@ void AndroidGraphicsManager::syncVirtkeyboardState(bool virtkeybd_on) { _forceRedraw = true; } -void AndroidGraphicsManager::touchControlDraw(int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) { +void AndroidGraphicsManager::touchControlInitSurface(const Graphics::ManagedSurface &surf) { + if (_touchcontrols->getWidth() == surf.w && _touchcontrols->getHeight() == surf.h) { + return; + } + + _touchcontrols->allocate(surf.w, surf.h); + Graphics::Surface *dst = _touchcontrols->getSurface(); + + Graphics::crossBlit( + (byte *)dst->getPixels(), (const byte *)surf.getPixels(), + dst->pitch, surf.pitch, + surf.w, surf.h, + surf.format, dst->format); + _touchcontrols->updateGLTexture(); +} + +void AndroidGraphicsManager::touchControlDraw(uint8 alpha, int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) { _targetBuffer->enableBlend(OpenGL::Framebuffer::kBlendModeTraditionalTransparency); OpenGL::Pipeline *pipeline = getPipeline(); pipeline->activate(); + if (alpha != 255) { + pipeline->setColor(1.0f, 1.0f, 1.0f, alpha / 255.0f); + } pipeline->drawTexture(_touchcontrols->getGLTexture(), x, y, w, h, clip); + if (alpha != 255) { + pipeline->setColor(1.0f, 1.0f, 1.0f, 1.0f); + } } void AndroidGraphicsManager::touchControlNotifyChanged() { diff --git a/backends/graphics/android/android-graphics.h b/backends/graphics/android/android-graphics.h index 4ae9bae0c5e..c865f3cc29b 100644 --- a/backends/graphics/android/android-graphics.h +++ b/backends/graphics/android/android-graphics.h @@ -88,8 +88,9 @@ public: float getHiDPIScreenFactor() const override; + void touchControlInitSurface(const Graphics::ManagedSurface &surf) override; void touchControlNotifyChanged() override; - void touchControlDraw(int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) override; + void touchControlDraw(uint8 alpha, int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) override; protected: void setSystemMousePosition(const int x, const int y) override {} diff --git a/backends/graphics3d/android/android-graphics3d.cpp b/backends/graphics3d/android/android-graphics3d.cpp index bd4a2435255..aba80ddafcf 100644 --- a/backends/graphics3d/android/android-graphics3d.cpp +++ b/backends/graphics3d/android/android-graphics3d.cpp @@ -41,6 +41,7 @@ #include "common/tokenizer.h" #include "graphics/blit.h" +#include "graphics/managed_surface.h" #include "graphics/opengl/shader.h" #include "graphics/opengl/context.h" @@ -54,26 +55,6 @@ #define CONTEXT_RESET_ENABLE(gl_param) if (!(saved ## gl_param)) { GLCALL(glDisable(gl_param)); } #define CONTEXT_RESET_DISABLE(gl_param) if (saved ## gl_param) { GLCALL(glEnable(gl_param)); } -static GLES8888Texture *loadBuiltinTexture(JNI::BitmapResources resource) { - const Graphics::Surface *src = JNI::getBitmapResource(JNI::BitmapResources::TOUCH_ARROWS_BITMAP); - if (!src) { - error("Failed to fetch touch arrows bitmap"); - } - - GLES8888Texture *ret = new GLES8888Texture(); - ret->allocBuffer(src->w, src->h); - Graphics::Surface *dst = ret->surface(); - - Graphics::crossBlit( - (byte *)dst->getPixels(), (const byte *)src->getPixels(), - dst->pitch, src->pitch, - src->w, src->h, - src->format, dst->format); - - delete src; - return ret; -} - AndroidGraphics3dManager::AndroidGraphics3dManager() : _screenChangeID(0), _graphicsMode(0), @@ -94,7 +75,7 @@ AndroidGraphics3dManager::AndroidGraphics3dManager() : _mouse_hotspot(), _mouse_dont_scale(false), _show_mouse(false), - _touchcontrols_texture(nullptr), + _touchcontrols_texture(new GLES8888Texture()), _old_touch_mode(OSystem_Android::TOUCH_MODE_TOUCHPAD) { if (JNI::egl_bits_per_pixel == 16) { @@ -112,8 +93,6 @@ AndroidGraphics3dManager::AndroidGraphics3dManager() : } _mouse_texture = _mouse_texture_palette; - _touchcontrols_texture = loadBuiltinTexture(JNI::BitmapResources::TOUCH_ARROWS_BITMAP); - initSurface(); // in 3D, not in GUI @@ -218,7 +197,7 @@ void AndroidGraphics3dManager::initSurface() { if (_touchcontrols_texture) { _touchcontrols_texture->reinit(); } - dynamic_cast(g_system)->getTouchControls().init( + dynamic_cast(g_system)->getTouchControls().setDrawer( this, JNI::egl_surface_width, JNI::egl_surface_height); updateScreenRect(); @@ -256,7 +235,7 @@ void AndroidGraphics3dManager::deinitSurface() { _mouse_texture_palette->release(); } - dynamic_cast(g_system)->getTouchControls().init( + dynamic_cast(g_system)->getTouchControls().setDrawer( nullptr, 0, 0); if (_touchcontrols_texture) { _touchcontrols_texture->release(); @@ -286,7 +265,7 @@ void AndroidGraphics3dManager::resizeSurface() { initOverlay(); } - dynamic_cast(g_system)->getTouchControls().init( + dynamic_cast(g_system)->getTouchControls().setDrawer( this, JNI::egl_surface_width, JNI::egl_surface_height); updateScreenRect(); @@ -303,6 +282,9 @@ void AndroidGraphics3dManager::updateScreen() { return; } + // Sets _forceRedraw if needed + dynamic_cast(g_system)->getTouchControls().beforeDraw(); + if (!_force_redraw && !_game_texture->dirty() && !_overlay_texture->dirty() && @@ -1103,12 +1085,28 @@ bool AndroidGraphics3dManager::setState(const AndroidCommonGraphics::State &stat return true; } +void AndroidGraphics3dManager::touchControlInitSurface(const Graphics::ManagedSurface &surf) { + if (_touchcontrols_texture->width() == surf.w && _touchcontrols_texture->height() == surf.h) { + return; + } + + _touchcontrols_texture->allocBuffer(surf.w, surf.h); + Graphics::Surface *dst = _touchcontrols_texture->surface(); + + Graphics::crossBlit( + (byte *)dst->getPixels(), (const byte *)surf.getPixels(), + dst->pitch, surf.pitch, + surf.w, surf.h, + surf.format, dst->format); +} + void AndroidGraphics3dManager::touchControlNotifyChanged() { // Make sure we redraw the screen _force_redraw = true; } -void AndroidGraphics3dManager::touchControlDraw(int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) { +void AndroidGraphics3dManager::touchControlDraw(uint8 alpha, int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) { + _touchcontrols_texture->setAlpha(alpha / 255.f); _touchcontrols_texture->drawTexture(x, y, w, h, clip); } diff --git a/backends/graphics3d/android/android-graphics3d.h b/backends/graphics3d/android/android-graphics3d.h index 22ebae16386..92f9318abea 100644 --- a/backends/graphics3d/android/android-graphics3d.h +++ b/backends/graphics3d/android/android-graphics3d.h @@ -121,8 +121,9 @@ public: virtual Common::List getSupportedFormats() const override; #endif + void touchControlInitSurface(const Graphics::ManagedSurface &surf) override; void touchControlNotifyChanged() override; - void touchControlDraw(int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) override; + void touchControlDraw(uint8 alpha, int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) override; void syncVirtkeyboardState(bool virtkeybd_on) override; diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index a1ccd4f49d5..2dd3259e690 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -557,6 +557,10 @@ void OSystem_Android::initBackend() { _audio_thread_exit = false; pthread_create(&_audio_thread, 0, audioThreadFunc, this); + JNI::DPIValues dpi; + JNI::getDPI(dpi); + _touchControls.init(dpi[2]); + _graphicsManager = new AndroidGraphicsManager(); // renice this thread to boost the audio thread diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk index 03d421016b8..835095828a5 100644 --- a/backends/platform/android/android.mk +++ b/backends/platform/android/android.mk @@ -13,7 +13,7 @@ APK_MAIN = ScummVM-debug.apk APK_MAIN_RELEASE = ScummVM-release-unsigned.apk AAB_MAIN_RELEASE = ScummVM-release.aab -DIST_FILES_HELP = $(PATH_DIST)/android-help.zip +DIST_FILES_PLATFORM = $(PATH_DIST)/android-help.zip $(PATH_DIST)/gamepad.svg $(PATH_BUILD): $(MKDIR) $(PATH_BUILD) @@ -38,9 +38,9 @@ $(PATH_BUILD)/local.properties: configure.stamp | $(PATH_BUILD) $(PATH_BUILD)/src.properties: configure.stamp | $(PATH_BUILD) $(ECHO) "srcdir=$(realpath $(srcdir))\n" > $(PATH_BUILD)/src.properties -$(PATH_BUILD_ASSETS): $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_ENGINEDATA_BIG) $(DIST_FILES_SOUNDFONTS) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(DIST_FILES_HELP) | $(PATH_BUILD) +$(PATH_BUILD_ASSETS): $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_ENGINEDATA_BIG) $(DIST_FILES_SOUNDFONTS) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(DIST_FILES_PLATFORM) | $(PATH_BUILD) $(INSTALL) -d $(PATH_BUILD_ASSETS) - $(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_ENGINEDATA_BIG) $(DIST_FILES_SOUNDFONTS) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(DIST_FILES_HELP) $(PATH_BUILD_ASSETS)/ + $(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_ENGINEDATA_BIG) $(DIST_FILES_SOUNDFONTS) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(DIST_FILES_PLATFORM) $(PATH_BUILD_ASSETS)/ ifneq ($(DIST_FILES_SHADERS),) $(INSTALL) -d $(PATH_BUILD_ASSETS)/shaders $(INSTALL) -c -m 644 $(DIST_FILES_SHADERS) $(PATH_BUILD_ASSETS)/shaders diff --git a/backends/platform/android/jni-android.cpp b/backends/platform/android/jni-android.cpp index 1c0bb45807d..7a01ddbcb55 100644 --- a/backends/platform/android/jni-android.cpp +++ b/backends/platform/android/jni-android.cpp @@ -92,7 +92,6 @@ jmethodID JNI::_MID_isConnectionLimited = 0; jmethodID JNI::_MID_setWindowCaption = 0; jmethodID JNI::_MID_showVirtualKeyboard = 0; jmethodID JNI::_MID_showOnScreenControls = 0; -jmethodID JNI::_MID_getBitmapResource = 0; jmethodID JNI::_MID_setTouchMode = 0; jmethodID JNI::_MID_getTouchMode = 0; jmethodID JNI::_MID_setOrientation = 0; @@ -437,71 +436,6 @@ void JNI::showOnScreenControls(int enableMask) { } } -Graphics::Surface *JNI::getBitmapResource(BitmapResources resource) { - JNIEnv *env = JNI::getEnv(); - - jobject bitmap = env->CallObjectMethod(_jobj, _MID_getBitmapResource, (int) resource); - - if (env->ExceptionCheck()) { - LOGE("Can't get bitmap resource"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return nullptr; - } - - if (bitmap == nullptr) { - LOGE("Bitmap resource was not found"); - return nullptr; - } - - AndroidBitmapInfo bitmap_info; - if (AndroidBitmap_getInfo(env, bitmap, &bitmap_info) != ANDROID_BITMAP_RESULT_SUCCESS) { - LOGE("Error reading bitmap info"); - env->DeleteLocalRef(bitmap); - return nullptr; - } - - Graphics::PixelFormat fmt; - switch(bitmap_info.format) { - case ANDROID_BITMAP_FORMAT_RGBA_8888: -#ifdef SCUMM_BIG_ENDIAN - fmt = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); -#else - fmt = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); -#endif - break; - case ANDROID_BITMAP_FORMAT_RGBA_4444: - fmt = Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0); - break; - case ANDROID_BITMAP_FORMAT_RGB_565: - fmt = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); - break; - default: - LOGE("Bitmap has unsupported format"); - env->DeleteLocalRef(bitmap); - return nullptr; - } - - void *src_pixels = nullptr; - if (AndroidBitmap_lockPixels(env, bitmap, &src_pixels) != ANDROID_BITMAP_RESULT_SUCCESS) { - LOGE("Error locking bitmap pixels"); - env->DeleteLocalRef(bitmap); - return nullptr; - } - - Graphics::Surface *ret = new Graphics::Surface(); - ret->create(bitmap_info.width, bitmap_info.height, fmt); - ret->copyRectToSurface(src_pixels, bitmap_info.stride, - 0, 0, bitmap_info.width, bitmap_info.height); - - AndroidBitmap_unlockPixels(env, bitmap); - env->DeleteLocalRef(bitmap); - - return ret; -} - void JNI::setTouchMode(int touchMode) { JNIEnv *env = JNI::getEnv(); @@ -822,7 +756,6 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, FIND_METHOD(, isConnectionLimited, "()Z"); FIND_METHOD(, showVirtualKeyboard, "(Z)V"); FIND_METHOD(, showOnScreenControls, "(I)V"); - FIND_METHOD(, getBitmapResource, "(I)Landroid/graphics/Bitmap;"); FIND_METHOD(, setTouchMode, "(I)V"); FIND_METHOD(, getTouchMode, "()I"); FIND_METHOD(, setOrientation, "(I)V"); diff --git a/backends/platform/android/jni-android.h b/backends/platform/android/jni-android.h index 6535aa4330e..8c6d721bad6 100644 --- a/backends/platform/android/jni-android.h +++ b/backends/platform/android/jni-android.h @@ -46,10 +46,6 @@ private: virtual ~JNI(); public: - enum struct BitmapResources { - TOUCH_ARROWS_BITMAP = 0 - }; - static bool pause; static sem_t pause_sem; @@ -94,7 +90,6 @@ public: static bool isConnectionLimited(); static void showVirtualKeyboard(bool enable); static void showOnScreenControls(int enableMask); - static Graphics::Surface *getBitmapResource(BitmapResources resource); static void setTouchMode(int touchMode); static int getTouchMode(); static void setOrientation(int touchMode); @@ -157,7 +152,6 @@ private: static jmethodID _MID_setWindowCaption; static jmethodID _MID_showVirtualKeyboard; static jmethodID _MID_showOnScreenControls; - static jmethodID _MID_getBitmapResource; static jmethodID _MID_setTouchMode; static jmethodID _MID_getTouchMode; static jmethodID _MID_setOrientation; diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java index 69bd2f1efbc..ede71d2863f 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java @@ -84,7 +84,6 @@ public abstract class ScummVM implements SurfaceHolder.Callback, abstract protected void setWindowCaption(String caption); abstract protected void showVirtualKeyboard(boolean enable); abstract protected void showOnScreenControls(int enableMask); - abstract protected Bitmap getBitmapResource(int resource); abstract protected void setTouchMode(int touchMode); abstract protected int getTouchMode(); abstract protected void setOrientation(int orientation); diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java index 141806b06be..c51b2208363 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java @@ -777,23 +777,6 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis }); } - @Override - protected Bitmap getBitmapResource(int resource) { - int id; - switch(resource) { - case 0: // TOUCH_ARROWS_BITMAP - id = R.drawable.touch_arrows; - break; - default: - return null; - } - - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inScaled = false; - - return BitmapFactory.decodeResource(getResources(), id, opts); - } - @Override protected void setTouchMode(final int touchMode) { if (_events.getTouchMode() == touchMode) { diff --git a/backends/platform/android/touchcontrols.cpp b/backends/platform/android/touchcontrols.cpp index 380513daa5b..73aa8965e40 100644 --- a/backends/platform/android/touchcontrols.cpp +++ b/backends/platform/android/touchcontrols.cpp @@ -38,150 +38,174 @@ #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) { + _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); } -TouchControls::Function TouchControls::getFunction(int x, int y) { +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; } - float xPercent = float(x) / _screen_width; + // 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; + } - if (xPercent < 0.3) { - return kFunctionJoystick; - } else if (xPercent < 0.8) { + float xRatio = float(x) / _screen_width; + + if (xRatio < 0.3) { + return kFunctionLeft; + } else if (xRatio < 0.7) { 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::JOYSTICK_BUTTON_DPAD_DOWN; - state.clip = Common::Rect(256, 0, 384, 128); - } else if (dX > abs(dY)) { - state.main = Common::JOYSTICK_BUTTON_DPAD_RIGHT; - state.clip = Common::Rect(128, 0, 256, 128); - } else if (-dY > abs(dX)) { - state.main = Common::JOYSTICK_BUTTON_DPAD_UP; - state.clip = Common::Rect(0, 0, 128, 128); - } else if (-dX > abs(dY)) { - state.main = Common::JOYSTICK_BUTTON_DPAD_LEFT; - state.clip = Common::Rect(384, 0, 512, 128); - } else { - return; - } - - if (sqNorm > 20000) { - state.modifier = Common::JOYSTICK_BUTTON_RIGHT_SHOULDER; - } - -} - -void TouchControls::touchToCenterState(int dX, int dY, FunctionState &state) { - int sqNorm = dX * dX + dY * dY; - if (sqNorm < 50 * 50) { - state.main = Common::JOYSTICK_BUTTON_GUIDE; - } -} - -void TouchControls::touchToRightState(int dX, int dY, FunctionState &state) { - if (dX * dX + dY * dY < 100 * 100) { - return; - } - - if (dX > abs(dY)) { - // right - state.main = Common::JOYSTICK_BUTTON_RIGHT_STICK; - state.clip = Common::Rect(512, 128, 640, 256); - return; - } else if (-dX > abs(dY)) { - // left - state.main = Common::JOYSTICK_BUTTON_LEFT_STICK; - state.clip = Common::Rect(512, 0, 640, 128); - return; - } - - static Common::JoystickButton buttons[5] = { - Common::JOYSTICK_BUTTON_Y, Common::JOYSTICK_BUTTON_B, // top zone - Common::JOYSTICK_BUTTON_INVALID, // center - Common::JOYSTICK_BUTTON_A, Common::JOYSTICK_BUTTON_X // bottom zone - }; - - static int16 clips[5][4] = { - { 0, 128, 128, 256 }, // y - { 128, 128, 256, 256 }, // b - { 0, 0, 0, 0 }, // center - { 256, 128, 384, 256 }, // a - { 384, 128, 512, 256 } // x - }; - static const uint offset = (ARRAYSIZE(buttons) - 1) / 2; - - int idx = (dY / 100) + offset; - if (idx < 0) { - idx = 0; - } - if (idx >= ARRAYSIZE(buttons)) { - idx = ARRAYSIZE(buttons) - 1; - } - state.main = buttons[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 } -}; - -void TouchControls::init(TouchControlsDrawer *drawer, int width, int height) { +void TouchControls::setDrawer(TouchControlsDrawer *drawer, int width, int height) { _drawer = drawer; - _screen_width = width; - _screen_height = height; + _screen_width = width * SCALE_FACTOR_FXP; + _screen_height = height * SCALE_FACTOR_FXP; + + if (drawer) { + drawer->touchControlInitSurface(*_svg); + } } -TouchControls::Pointer *TouchControls::getPointerFromId(int ptrId, bool createNotFound) { - uint freeEntry = -1; - for (uint i = 0; i < kNumPointers; i++) { - Pointer &ptr = _pointers[i]; - if (ptr.active && (ptr.id == ptrId)) { - return &ptr; +TouchControls::Function *TouchControls::getFunctionFromPointerId(int ptrId) { + for (uint i = 0; i < kFunctionCount; i++) { + Function *func = _functions[i]; + if (func->status != kFunctionActive) { + continue; } - if (createNotFound && (freeEntry == -1) && !ptr.active) { - freeEntry = i; + if (func->pointerId == ptrId) { + return func; } } - // Too much fingers or not found - if (freeEntry == -1) { + return nullptr; +} + +TouchControls::Function *TouchControls::getZombieFunctionFromPos(int x, int y) { + if (!_zombieCount) { return nullptr; } - - Pointer &ptr = _pointers[freeEntry]; - ptr.reset(); - ptr.id = ptrId; - - return &ptr; -} - -TouchControls::Pointer *TouchControls::findPointerFromFunction(Function function) { - for (uint i = 0; i < kNumPointers; i++) { - Pointer &ptr = _pointers[i]; - if (ptr.active && (ptr.function == function)) { - return &ptr; + 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; @@ -190,130 +214,119 @@ TouchControls::Pointer *TouchControls::findPointerFromFunction(Function function void TouchControls::draw() { assert(_drawer != nullptr); - for (uint i = 0; i < kFunctionMax + 1; i++) { - FunctionState &state = _functionStates[i]; - FunctionBehavior behavior = functionBehaviors[i]; + uint32 now = g_system->getMillis(true); - if (state.clip.isEmpty()) { + 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; } - _drawer->touchControlDraw(_screen_width * behavior.xRatio, _screen_height * behavior.yRatio, - 64, 64, state.clip); - + 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) { - Pointer *ptr = getPointerFromId(ptrId, true); - if (!ptr) { - return; + 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; } - TouchControls::Function function = getFunction(x, y); - // ptrId is active no matter what - ptr->active = true; - if (function == kFunctionNone) { - // No function for this finger - return; - } - if (findPointerFromFunction(function)) { - // Some finger is already using this function: don't do anything - return; - } + func->status = kFunctionActive; + func->pointerId = ptrId; + func->currentX = x; + func->currentY = y; - ptr->startX = ptr->currentX = x; - ptr->startY = ptr->currentY = y; - ptr->function = function; + int dX = x - func->startX; + int dY = y - func->startY; + + func->touch(dX, dY, action); + if (_drawer) { + _drawer->touchControlNotifyChanged(); + } } else if (action == JACTION_MOVE) { - Pointer *ptr = getPointerFromId(ptrId, false); - if (!ptr || ptr->function == kFunctionNone) { + Function *func = getFunctionFromPointerId(ptrId); + if (!func) { return; } - FunctionBehavior &behavior = functionBehaviors[ptr->function]; + func->currentX = x; + func->currentY = y; - ptr->currentX = x; - ptr->currentY = y; + int dX = x - func->startX; + int dY = y - func->startY; - 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.pressOnRelease) { - // send key presses continuously - // first old remove main key, then update modifier, then press new main key - if (oldState.main != newState.main) { - buttonUp(oldState.main); - } - if (oldState.modifier != newState.modifier) { - buttonUp(oldState.modifier); - buttonDown(newState.modifier); - } - if (oldState.main != newState.main) { - buttonDown(newState.main); - } - } - oldState = newState; + func->touch(dX, dY, action); if (_drawer) { _drawer->touchControlNotifyChanged(); } } else if (action == JACTION_UP) { - Pointer *ptr = getPointerFromId(ptrId, false); - if (!ptr || ptr->function == kFunctionNone) { + Function *func = getFunctionFromPointerId(ptrId); + if (!func) { return; } - FunctionBehavior &behavior = functionBehaviors[ptr->function]; - FunctionState &functionState = _functionStates[ptr->function]; + func->currentX = x; + func->currentY = y; - if (!behavior.pressOnRelease) { - // We sent key down continuously: buttonUp everything - buttonUp(functionState.main); - buttonUp(functionState.modifier); - } else { - int dX = x - ptr->startX; - int dY = y - ptr->startY; + int dX = x - func->startX; + int dY = y - func->startY; - FunctionState newState; - functionBehaviors[ptr->function].touchToState(dX, dY, newState); - - buttonDown(newState.modifier); - buttonPress(newState.main); - buttonUp(newState.modifier); - } - - functionState.reset(); - ptr->active = false; + 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 < kNumPointers; i++) { - Pointer &ptr = _pointers[i]; - ptr.reset(); - } + for (uint i = 0; i < kFunctionCount; i++) { + Function *func = _functions[i]; - for (uint i = 0; i < kFunctionMax + 1; i++) { - FunctionBehavior &behavior = functionBehaviors[i]; - FunctionState &functionState = _functionStates[i]; - - if (!behavior.pressOnRelease) { - // We sent key down continuously: buttonUp everything - buttonUp(functionState.main); - buttonUp(functionState.modifier); + if (func->status == kFunctionActive) { + func->touch(0, 0, action); } - - functionState.reset(); + func->reset(); } if (_drawer) { _drawer->touchControlNotifyChanged(); } + _zombieCount = 0; } } @@ -322,6 +335,7 @@ void TouchControls::buttonDown(Common::JoystickButton jb) { return; } + //LOGD("TouchControls::buttonDown: %d", jb); Common::Event ev; ev.type = Common::EVENT_JOYBUTTON_DOWN; ev.joystick.button = jb; @@ -333,6 +347,7 @@ void TouchControls::buttonUp(Common::JoystickButton jb) { return; } + //LOGD("TouchControls::buttonUp: %d", jb); Common::Event ev; ev.type = Common::EVENT_JOYBUTTON_UP; ev.joystick.button = jb; @@ -344,6 +359,7 @@ void TouchControls::buttonPress(Common::JoystickButton jb) { return; } + //LOGD("TouchControls::buttonPress: %d", jb); Common::Event ev1, ev2; ev1.type = Common::EVENT_JOYBUTTON_DOWN; ev1.joystick.button = jb; @@ -351,3 +367,388 @@ void TouchControls::buttonPress(Common::JoystickButton jb) { 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) { + if (action == JACTION_CANCEL) { + return; + } + + // 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) { + button = 4; + } else { + button = 2; + } + } else { + // Y or A + if (dY < 0) { + button = 1; + } else { + button = 3; + } + + } + } else { + button = 0; + } + + 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_UP && button) { + buttonDown(modifiers[button - 1]); + buttonPress(buttons[button - 1]); + buttonUp(modifiers[button - 1]); + button = 0; + } +} + +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) { + if (action == JACTION_CANCEL) { + return; + } + + // 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) { + button = 4; + } else { + button = 2; + } + } else { + // Y or A + if (dY < 0) { + button = 1; + } else { + button = 3; + } + + } + } else { + button = 0; + } + + 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_UP && button) { + buttonDown(modifiers[button - 1]); + buttonPress(buttons[button - 1]); + buttonUp(modifiers[button - 1]); + button = 0; + } +} + +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); +} diff --git a/backends/platform/android/touchcontrols.h b/backends/platform/android/touchcontrols.h index 09811a7c8af..01d10b8c12f 100644 --- a/backends/platform/android/touchcontrols.h +++ b/backends/platform/android/touchcontrols.h @@ -24,10 +24,15 @@ #include "common/events.h" +namespace Graphics { +class ManagedSurface; +} + class TouchControlsDrawer { public: + virtual void touchControlInitSurface(const Graphics::ManagedSurface &surf) = 0; virtual void touchControlNotifyChanged() = 0; - virtual void touchControlDraw(int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) = 0; + virtual void touchControlDraw(uint8 alpha, int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) = 0; protected: ~TouchControlsDrawer() {} @@ -44,81 +49,126 @@ public: }; TouchControls(); + ~TouchControls(); - void init(TouchControlsDrawer *drawer, int width, int height); + void init(float scale); + void setDrawer(TouchControlsDrawer *drawer, int width, int height); + void beforeDraw(); void draw(); void update(Action action, int ptr, int x, int y); private: TouchControlsDrawer *_drawer; - int _screen_width, _screen_height; + unsigned int _screen_width, _screen_height; + unsigned int _scale, _scale2; - enum Function { - kFunctionNone = -1, - kFunctionJoystick = 0, - kFunctionCenter = 1, - kFunctionRight = 2, - kFunctionMax = 2 + Graphics::ManagedSurface *_svg; + + unsigned int _zombieCount; + + enum State { + kFunctionInactive = 0, + kFunctionActive = 1, + kFunctionZombie = 2 }; - Function getFunction(int x, int y); - struct Pointer { - Pointer() : id(-1), startX(-1), startY(-1), + struct Function { + virtual bool isInside(int, int) = 0; + virtual void touch(int, int, Action) = 0; + virtual void draw(uint8 alpha) = 0; + virtual void resetState() {} + + Function(const TouchControls *parent_) : + parent(parent_), pointerId(-1), + startX(-1), startY(-1), currentX(-1), currentY(-1), - function(kFunctionNone), active(false) {} + lastActivable(0), status(kFunctionInactive) {} + virtual ~Function() {} + void reset() { - id = -1; + pointerId = -1; startX = startY = currentX = currentY = -1; - function = kFunctionNone; - active = false; + lastActivable = 0; + status = kFunctionInactive; + resetState(); } - int id; + const TouchControls *parent; + + int pointerId; uint16 startX, startY; uint16 currentX, currentY; - Function function; - bool active; + uint32 lastActivable; + State status; + }; + Function *getFunctionFromPointerId(int ptr); + Function *getZombieFunctionFromPos(int x, int y); + + enum FunctionId { + kFunctionNone = -1, + kFunctionLeft = 0, + kFunctionRight = 1, + kFunctionCenter = 2, + kFunctionCount = 3 + }; + FunctionId getFunctionId(int x, int y); + + Function *_functions[kFunctionCount]; + + static void buttonDown(Common::JoystickButton jb); + static void buttonUp(Common::JoystickButton jb); + static void buttonPress(Common::JoystickButton jb); + + /** + * Draws a part of the joystick surface on the screen + * + * @param x The left coordinate in fixed-point screen pixels + * @param y The top coordinate in fixed-point screen pixels + * @param offX The left offset in SVG pixels + * @param offY The top offset in SVG pixels + * @param clip The clipping rectangle in source surface in SVG pixels + */ + void drawSurface(uint8 alpha, int x, int y, int offX, int offY, const Common::Rect &clip) const; + + + // Functions implementations + struct FunctionLeft : Function { + FunctionLeft(const TouchControls *parent) : + Function(parent), mask(0) {} + void resetState() override { mask = 0; } + + bool isInside(int, int) override; + void touch(int, int, Action) override; + void draw(uint8 alpha) override; + + uint32 mask; + void maskToLeftButtons(uint32 oldMask, uint32 newMask); }; - enum { kNumPointers = 5 }; - Pointer _pointers[kNumPointers]; + struct FunctionRight : Function { + FunctionRight(const TouchControls *parent) : + Function(parent), button(0) {} + void resetState() override { button = 0; } - Pointer *getPointerFromId(int ptr, bool createNotFound); - Pointer *findPointerFromFunction(Function function); + bool isInside(int, int) override; + void touch(int, int, Action) override; + void draw(uint8 alpha) override; - struct FunctionState { - FunctionState() : main(Common::JOYSTICK_BUTTON_INVALID), - modifier(Common::JOYSTICK_BUTTON_INVALID) {} - void reset() { - main = Common::JOYSTICK_BUTTON_INVALID; - modifier = Common::JOYSTICK_BUTTON_INVALID; - clip = Common::Rect(); - } - - Common::JoystickButton main; - Common::JoystickButton modifier; - Common::Rect clip; + uint32 button; }; - FunctionState _functionStates[kFunctionMax + 1]; + struct FunctionCenter : Function { + FunctionCenter(const TouchControls *parent) : + Function(parent), button(0) {} + void resetState() override { button = 0; } - void buttonDown(Common::JoystickButton jb); - void buttonUp(Common::JoystickButton jb); - void buttonPress(Common::JoystickButton jb); + bool isInside(int, int) override; + void touch(int, int, Action) override; + void draw(uint8 alpha) override; - /* Functions implementations */ - struct FunctionBehavior { - void (*touchToState)(int, int, TouchControls::FunctionState &); - bool pressOnRelease; - float xRatio; - float yRatio; + uint32 button; }; - 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 diff --git a/dists/android/gamepad.svg b/dists/android/gamepad.svg new file mode 100644 index 00000000000..6595e5526d5 --- /dev/null +++ b/dists/android/gamepad.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dists/android/res/drawable/touch_arrows.png b/dists/android/res/drawable/touch_arrows.png deleted file mode 100644 index 816dabfd998..00000000000 Binary files a/dists/android/res/drawable/touch_arrows.png and /dev/null differ