mirror of
https://github.com/libretro/scummvm.git
synced 2025-03-03 00:35:54 +00:00
ANDROID: Rework the virtual gamepad controller
- Use a SVG asset to allow for better scalability - Make the controller visible when touching the screen - Allow for oblique moving by placing finger between two directions - Add more buttons on the center area of the screen (GUIDE, START, LEFT STICK, RIGHT STICK) and simplify right area (only four buttons) - Don't track all fingers, only the ones needed - Cleanup now unused code
This commit is contained in:
parent
5065891f0c
commit
a98df4e445
@ -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<OSystem_Android *>(g_system)->applyTouchSettings(false, false);
|
||||
dynamic_cast<OSystem_Android *>(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<OSystem_Android *>(g_system)->getTouchControls().init(
|
||||
dynamic_cast<OSystem_Android *>(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<OSystem_Android *>(g_system)->getTouchControls().init(
|
||||
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().setDrawer(
|
||||
nullptr, 0, 0);
|
||||
if (_touchcontrols) {
|
||||
_touchcontrols->destroy();
|
||||
@ -157,7 +140,7 @@ void AndroidGraphicsManager::resizeSurface() {
|
||||
error("JNI::initSurface failed");
|
||||
}
|
||||
|
||||
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().init(
|
||||
dynamic_cast<OSystem_Android *>(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<OSystem_Android *>(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() {
|
||||
|
@ -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 {}
|
||||
|
@ -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<OSystem_Android *>(g_system)->getTouchControls().init(
|
||||
dynamic_cast<OSystem_Android *>(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<OSystem_Android *>(g_system)->getTouchControls().init(
|
||||
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().setDrawer(
|
||||
nullptr, 0, 0);
|
||||
if (_touchcontrols_texture) {
|
||||
_touchcontrols_texture->release();
|
||||
@ -286,7 +265,7 @@ void AndroidGraphics3dManager::resizeSurface() {
|
||||
initOverlay();
|
||||
}
|
||||
|
||||
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().init(
|
||||
dynamic_cast<OSystem_Android *>(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<OSystem_Android *>(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);
|
||||
}
|
||||
|
||||
|
@ -121,8 +121,9 @@ public:
|
||||
virtual Common::List<Graphics::PixelFormat> 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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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<OSystem_Android *>(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);
|
||||
}
|
||||
|
@ -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
|
||||
|
90
dists/android/gamepad.svg
Normal file
90
dists/android/gamepad.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<svg width="384" height="256" version="1.1" viewBox="0 0 384 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(64,64)">
|
||||
<path d="m62 0a62 62 0 0 1-62 62 62 62 0 0 1-62-62 62 62 0 0 1 62-62 62 62 0 0 1 62 62z" fill="#808080" fill-opacity=".4"/>
|
||||
<g fill-opacity=".4" fill-rule="evenodd" stroke="#808080" stroke-opacity=".8">
|
||||
<path d="m-18-48h36v27l-18 18-18-18z"/>
|
||||
<path d="m48-18v36h-27l-18-18 18-18z"/>
|
||||
<path d="m18 48h-36v-27l18-18 18 18z"/>
|
||||
<path d="m-48 18v-36h27l18 18-18 18z"/>
|
||||
</g>
|
||||
<g fill="none" fill-rule="evenodd" stroke="#808080" stroke-opacity=".2">
|
||||
<path d="m-16-56 16-4 16 4"/>
|
||||
<path d="m56-16 4 16-4 16"/>
|
||||
<path d="m16 56-16 4-16-4"/>
|
||||
<path d="m-56 16-4-16 4-16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(32,160)" fill="#fff" fill-rule="evenodd" stroke="#808080">
|
||||
<path d="m-18-16h36v27l-18 18-18-18z"/>
|
||||
<path d="m80-18v36h-27l-18-18 18-18z"/>
|
||||
<path d="m18 80h-36v-27l18-18 18 18z"/>
|
||||
<path d="m48 82v-36h27l18 18-18 18z"/>
|
||||
</g>
|
||||
<g transform="translate(32,160)" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="2">
|
||||
<path d="m-16-24 16-4 16 4"/>
|
||||
<path d="m88-16 4 16-4 16"/>
|
||||
<path d="m16 88-16 4-16-4"/>
|
||||
<path d="m40 80-4-16 4-16"/>
|
||||
</g>
|
||||
<g transform="translate(192,64)">
|
||||
<path d="m62 0a62 62 0 0 1-62 62 62 62 0 0 1-62-62 62 62 0 0 1 62-62 62 62 0 0 1 62 62z" fill="#808080" fill-opacity=".4"/>
|
||||
<g transform="translate(0,-36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m3.3496 3.0547v11.945h-6.3164v-11.945l-9.9688-17.037h6.6387l6.4453 12.16 6.5312-12.16h6.6387z" fill="#ffd733" aria-label="Y"/>
|
||||
</g>
|
||||
<g transform="translate(36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m12.48 6.7285q0 3.8887-2.6641 6.0801-2.6641 2.1914-7.3691 2.1914h-12.504v-28.982h11.279q4.6836 0 7.0898 1.8906 2.4277 1.8691 2.4277 5.3926 0 2.4707-1.418 4.168-1.418 1.6758-4.3613 2.2988 3.6738 0.38672 5.5859 2.1914 1.9336 1.8047 1.9336 4.7695zm-8.1211-12.568q0-3.4375-3.5879-3.4375h-4.4902v6.8535h4.5332q3.5449 0 3.5449-3.416zm1.7402 12.031q0-1.8906-1.1387-2.9004-1.1387-1.0312-3.3086-1.0312h-5.3711v8.0352h5.5215q4.2969 0 4.2969-4.1035z" fill="#f33" aria-label="B"/>
|
||||
</g>
|
||||
<g transform="translate(0,36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m13.404 15h-6.3164l-1.9766-7.0898h-9.7969l-1.9766 7.0898h-6.3379l9.5176-28.982h7.3906zm-13.191-25.072q-0.17188 0.90234-0.64453 2.707-0.45117 1.7832-2.9219 10.506h7.1328q-2.5137-8.8516-2.9648-10.592-0.42969-1.7402-0.60156-2.6211z" fill="#3f3" aria-label="A"/>
|
||||
</g>
|
||||
<g transform="translate(-36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m6.7441 15-6.5098-10.592-6.5312 10.592h-6.7031l9.8613-15.275-8.9805-13.707h6.7031l5.6504 9.2383 5.6289-9.2383h6.6602l-9.1953 13.707 10.076 15.275z" fill="#33f" aria-label="X"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(160,160)" stroke="#808080">
|
||||
<path d="m24-4a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill="#ffd733"/>
|
||||
<path d="m92 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill="#f33"/>
|
||||
<path d="m24 68a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill="#3f3"/>
|
||||
<path d="m84 64a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill="#33f"/>
|
||||
</g>
|
||||
<g transform="translate(320,64)">
|
||||
<path d="m62 0a62 62 0 0 1-62 62 62 62 0 0 1-62-62 62 62 0 0 1 62-62 62 62 0 0 1 62 62z" fill="#808080" fill-opacity=".4"/>
|
||||
<g transform="translate(0,-36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<g transform="translate(-16,-20)" stroke-width=".080658">
|
||||
<path d="m29.088 19.268c1.9849 2.3662 2.7235 6.6713 2.4405 8.9046-0.42208 3.3316-1.9359 6.5163-4.4324 8.3948-2.5977 1.9549-5.7752 3.1121-9.741 3.3791-5.9662 0.40054-10.606-1.4999-13.691-4.3769-1.5813-1.4765-4.0645-3.5962-3.0067-6.6043 0.54452-1.5481 2.5441-3.4614 3.8371-4.7854 0.46435-0.47556 1.3787-1.3564 1.279-1.9332-0.22891-0.97692-1.7271-2.4455-2.3604-3.4873-1.8986-2.9765-2.3227-7.4998-0.65171-11.259 1.9924-4.4807 7.0057-7.3285 13.861-7.0827 2.838 0.10179 6.2954 1.0368 8.6715 2.4034 3.3942 1.9507 6.1567 4.2012 5.8079 6.6797-0.21439 1.5229-1.346 3.2141-2.6848 4.8449-0.83037 1.0115-1.216 1.6294-1.1682 2.2229 0.04065 0.5037 1.2677 2.0168 1.8397 2.6986" opacity=".3"/>
|
||||
<path d="m28.685 18.865c1.9849 2.3662 2.7235 6.6713 2.4405 8.9046-0.42208 3.3316-1.9359 6.5163-4.4325 8.3948-2.5977 1.9549-5.7752 3.1121-9.741 3.3791-5.9662 0.40054-10.606-1.4999-13.691-4.3769-1.5813-1.4765-4.0645-3.5962-3.0067-6.6043 0.54452-1.5481 2.5441-3.4614 3.8371-4.7854 0.46434-0.47556 1.3787-1.3564 1.279-1.9332-0.22891-0.97692-1.7271-2.4455-2.3604-3.4873-1.8986-2.9765-2.3227-7.4998-0.65171-11.259 1.9924-4.4807 7.0057-7.3285 13.861-7.0827 2.838 0.10179 6.2954 1.0368 8.6715 2.4034 3.3942 1.9507 6.1567 4.2012 5.8079 6.6797-0.21439 1.5229-1.346 3.2141-2.6848 4.8449-0.83037 1.0115-1.216 1.6294-1.1682 2.2229 0.04065 0.50371 1.2677 2.0168 1.8397 2.6986" fill="#333"/>
|
||||
<g fill="#00721d">
|
||||
<path d="m27.778 8.29-2.7389 4.149v0.0062c-0.04291 0.06107-0.10136 0.12276-0.15207 0.16752-0.14412 0.12258-0.32261 0.1914-0.50195 0.1914-0.11486 0-0.2382-0.03322-0.34813-0.08605v0.0036c-0.06298-0.03098-0.12371-0.07051-0.18103-0.11943-0.03436-0.02928-0.68677-0.58475-1.4468-1.0681-0.71729-0.45475-1.4261-0.86126-2.1151-1.1924l-1.61e-4 0.01155c-0.03923-0.01889-0.07858-0.03746-0.11774-0.056-0.7289-0.34166-1.4096-0.59904-2.0434-0.76987-0.63373-0.17091-1.1877-0.25736-1.6625-0.25736-0.48643 0-0.86336 0.10201-1.1305 0.30576v-0.011706l-0.01598 0.00902c-0.02299 0.020245-0.09185 0.095192-0.11298 0.11721-0.26932 0.2802-0.40351 0.63779-0.40351 1.0739v1.4005c0-0.43603 0.13421-0.79354 0.40351-1.0737 0.02113-0.02202 0.1063-0.09001 0.12929-0.11025v-0.0046c0.26711-0.20359 0.64394-0.30561 1.1302-0.30561 0.47483 0 1.0288 0.08653 1.6625 0.25736 0.63373 0.17083 1.3145 0.42829 2.0434 0.77004 0.03554 0.01668 0.07121 0.03382 0.10682 0.05093v0.0043c0.68898 0.3311 1.4088 0.72664 2.1261 1.1815 0.76004 0.48322 1.4125 1.0389 1.4468 1.0681 0.05712 0.04875 0.11764 0.08817 0.1804 0.11912v0.0016c0.10994 0.05291 0.23391 0.08115 0.34876 0.08115 0.17752 0 0.35166-0.06213 0.49483-0.18191v0.0046c0.05493-0.04847 0.13898-0.13768 0.18246-0.20358l-1.61e-4 -0.01234 2.7157-4.1208z"/>
|
||||
<path d="m17.754 26.593v1.4005c0-0.59122-0.32594-1.1509-0.97418-1.6807-0.6497-0.52903-1.4576-1.0809-2.4238-1.6578-0.9662-0.57549-2.0116-1.1974-3.136-1.868-1.1281-0.67204-2.1799-1.4619-3.1352-2.357-0.96692-0.90215-1.7741-1.938-2.4238-3.1047-0.64897-1.1674-0.97346-2.5135-0.97346-4.0391v-1.4006c0 1.5256 0.32448 2.8716 0.97346 4.0391 0.64978 1.1667 1.4569 2.2026 2.4238 3.1048 0.95531 0.89498 2.0072 1.6849 3.1352 2.3569 1.1244 0.67067 2.1698 1.2926 3.136 1.868 0.9662 0.57694 1.7741 1.1289 2.4238 1.6578 0.64824 0.52968 0.97418 1.0895 0.97418 1.6807"/>
|
||||
<path d="m27.969 25.612c0 1.6807-0.35639 3.1362-1.0693 4.365-0.5254 0.90683-1.1667 1.6945-1.9242 2.3641v1.4004c0.75754-0.66946 1.3988-1.4569 1.9242-2.3637 0.71285-1.2289 1.0693-2.6843 1.0693-4.365zm-24.755 3.3622v1.4005c1.584 2.1476 3.3654 3.7511 5.3449 4.8092 0.11731 0.06258 0.23654 0.12295 0.35715 0.18191l-6.453e-4 0.03069c1.9013 0.91345 4.2758 1.3743 6.9847 1.3743 1.4562 0 2.9058-0.19509 4.3475-0.58338 1.4401-0.38958 2.7323-0.9808 3.8719-1.7743 0.29988-0.20882 0.58502-0.43274 0.85529-0.67164v-1.4005c-0.27028 0.23891-0.55542 0.46274-0.85529 0.67164-1.1396 0.79351-2.4317 1.3846-3.8719 1.7743-1.4417 0.3882-2.8913 0.58338-4.3475 0.58338-2.7089 0-5.057-0.50562-6.9582-1.4191l-4.84e-4 0.02642c-0.12932-0.062824-0.25693-0.12736-0.38247-0.19441-1.9796-1.058-3.761-2.6616-5.3449-4.809z"/>
|
||||
</g>
|
||||
<path d="m15.235 2.827c2.5335 0 4.8063 0.45967 6.8178 1.3776 2.0115 0.91861 3.92 2.2805 5.7253 4.0849l-2.7331 4.1463c-0.15898 0.24133-0.40611 0.36772-0.65994 0.36772-0.18309 0-0.36973-0.06582-0.52911-0.2018-0.03436-0.02928-0.68672-0.58477-1.4468-1.0681-0.76012-0.48185-1.5048-0.8936-2.2329-1.2368-0.72882-0.34167-1.4097-0.59904-2.0435-0.76988-0.63372-0.17091-1.1876-0.25738-1.6624-0.25738-0.57049 0-0.9908 0.1401-1.2594 0.42031-0.26932 0.28028-0.40361 0.63776-0.40361 1.0738 0 0.55976 0.32448 1.0817 0.9741 1.5642 0.64824 0.48257 1.4643 0.98007 2.4463 1.4941 1.0628 0.55831 2.1169 1.1339 3.16 1.7272 1.1237 0.6384 2.1769 1.409 3.1592 2.3112 0.98217 0.90361 1.7981 1.9852 2.4478 3.2456 0.64897 1.2604 0.97418 2.7624 0.97418 4.5052 0 1.6807-0.35643 3.1363-1.0693 4.3651-0.71285 1.2303-1.6391 2.2412-2.7795 3.0355-1.1397 0.79351-2.4318 1.3847-3.872 1.7744-1.4417 0.38812-2.8913 0.58332-4.3475 0.58332-2.9146 0-5.3616-0.52976-7.3412-1.587-1.9796-1.0581-3.761-2.6615-5.3449-4.809l3.3755-4.1964c0.16164-0.2006 0.38127-0.30174 0.60211-0.30174 0.20648 0 0.41401 0.08848 0.57606 0.26673 0.019358 0.02129 0.38764 0.42603 1.0533 0.94006 0.67365 0.51968 1.3981 0.97289 2.1618 1.3533 0.76294 0.38466 1.5571 0.70487 2.3752 0.95724 0.80795 0.2502 1.56 0.37385 2.2569 0.37385 0.69616 0 1.2275-0.12437 1.5919-0.37385 0.36368-0.24883 0.54654-0.71559 0.54654-1.4005 0-0.59122-0.32594-1.151-0.97418-1.6807-0.64962-0.52895-1.4576-1.0809-2.4238-1.6578-0.9662-0.57541-2.0115-1.1974-3.136-1.868-1.1281-0.67196-2.1799-1.4619-3.1352-2.3569-0.96692-0.90223-1.7741-1.9381-2.4238-3.1048-0.64897-1.1674-0.97346-2.5135-0.97346-4.0391 0-1.4942 0.28456-2.801 0.85513-3.9212 0.56985-1.121 1.3379-2.0618 2.3041-2.8253 0.9662-0.76205 2.0827-1.3375 3.3494-1.7271 1.2667-0.38893 2.6133-0.58412 4.039-0.58412" fill="#00c832"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m-6.0156 10.5-4.5312-8.0469h-2.7031v8.0469h-4.6094v-21.078h7.7188q4.125 0 6.1719 1.6094t2.0469 4.6406q0 2.1406-1.1562 3.6406t-3.2031 2.1094l5.4531 9.0781zm-0.54688-14.641q0-1.4688-0.96875-2.1562-0.96875-0.70312-3.0938-0.70312h-2.625v5.875h2.75q3.9375 0 3.9375-3.0156zm23.969 8.5312q0 3-2.2969 4.7188-2.2812 1.7031-6.3594 1.7031-3.6719 0-5.9844-1.5469t-2.9688-4.5l4.4531-0.57812q0.35938 1.4688 1.5 2.3281 1.1406 0.84375 3.125 0.84375 2.0625 0 3.0469-0.65625 1-0.65625 1-2.0469 0-1.0312-0.875-1.7344-0.875-0.71875-2.5469-1.125-3.3594-0.8125-4.75-1.4062-1.375-0.60938-2.2031-1.3594t-1.2812-1.75q-0.4375-1.0156-0.4375-2.2656 0-2.7344 2.1875-4.3281 2.2031-1.5938 5.7969-1.5938 3.5 0 5.4688 1.3906 1.9688 1.375 2.5469 4.2812l-4.4688 0.45312q-0.6875-2.8594-3.6406-2.8594-1.6562 0-2.5312 0.625-0.875 0.60938-0.875 1.7656 0 0.76562 0.42188 1.2812 0.42188 0.5 1.1406 0.85938 0.73438 0.34375 2.9688 0.92188 2.9375 0.71875 4.5156 1.6094 1.5781 0.875 2.3125 2.125t0.73438 2.8438z" fill="#c0c0c0" aria-label="RS"/>
|
||||
</g>
|
||||
<g transform="translate(0,36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m-11.812-0.1875c0.32476-20.46 0.32476-20.46 17.881-9.9487 17.556 10.511 17.556 10.511-0.32476 20.46-17.881 9.9487-17.881 9.9487-17.556-10.511z" fill="#c0c0c0" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g transform="translate(-36)">
|
||||
<path d="m24 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill-opacity=".4" stroke="#808080" stroke-opacity=".8"/>
|
||||
<path d="m-16.703 10.5v-21.078h4.6094v17.516h9.625v3.5625zm34.109-6.1094q0 3-2.2969 4.7188-2.2812 1.7031-6.3594 1.7031-3.6719 0-5.9844-1.5469t-2.9688-4.5l4.4531-0.57812q0.35938 1.4688 1.5 2.3281 1.1406 0.84375 3.125 0.84375 2.0625 0 3.0469-0.65625 1-0.65625 1-2.0469 0-1.0312-0.875-1.7344-0.875-0.71875-2.5469-1.125-3.3594-0.8125-4.75-1.4062-1.375-0.60938-2.2031-1.3594t-1.2812-1.75q-0.4375-1.0156-0.4375-2.2656 0-2.7344 2.1875-4.3281 2.2031-1.5938 5.7969-1.5938 3.5 0 5.4688 1.3906 1.9688 1.375 2.5469 4.2812l-4.4688 0.45312q-0.6875-2.8594-3.6406-2.8594-1.6562 0-2.5312 0.625-0.875 0.60938-0.875 1.7656 0 0.76562 0.42188 1.2812 0.42188 0.5 1.1406 0.85938 0.73438 0.34375 2.9688 0.92188 2.9375 0.71875 4.5156 1.6094 1.5781 0.875 2.3125 2.125t0.73438 2.8438z" fill="#c0c0c0" aria-label="LS"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(288,160)" stroke="#808080">
|
||||
<path d="m24-4a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z" fill="#00c832"/>
|
||||
<g fill="#c0c0c0">
|
||||
<path d="m92 0a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z"/>
|
||||
<path d="m24 68a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z"/>
|
||||
<path d="m84 64a24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24 24 24 0 0 1 24 24z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
Loading…
x
Reference in New Issue
Block a user