From 68011fa8f254c1b352778a841d96a5af1f18a36a Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 26 Apr 2015 18:40:05 -0700 Subject: [PATCH] First cut at touch menu HUD - Enables choosing touch keyboard or touch joystick - Enables increasing/decreasing speed - Enables opening main popup menu - Also migrates all gesture responsibility away from Java side of things --- .../org/deadc0de/apple2ix/Apple2Activity.java | 95 +-- .../org/deadc0de/apple2ix/Apple2View.java | 5 - Android/jni/jnihooks.c | 86 +-- Android/jni/sources.mk | 1 + src/font.c | 8 +- src/font.txt | 34 +- src/interface.c | 2 + src/interface.h | 6 + src/video/Basic.fsh | 8 + src/video/gltouchmenu.c | 678 ++++++++++++++++++ src/video/glvideo.c | 24 + src/video/glvideo.h | 6 + src/video/video.h | 14 +- 13 files changed, 787 insertions(+), 180 deletions(-) create mode 100644 src/video/gltouchmenu.c diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java index 12f461e5..904d3387 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java @@ -44,14 +44,9 @@ public class Apple2Activity extends Activity { private Apple2View mView = null; private AlertDialog mQuitDialog = null; private AlertDialog mRebootDialog = null; - private GestureDetector mDetector = null; - private boolean mSwipeTogglesSpeed = true; - private boolean mDoubleTapShowsKeyboard = true; - private boolean mSingleTapShowsMainMenu = true; private int mWidth = 0; private int mHeight = 0; - private boolean mSoftKeyboardShowing = false; private float[] mXCoords = new float[MAX_FINGERS]; private float[] mYCoords = new float[MAX_FINGERS]; @@ -65,8 +60,6 @@ public class Apple2Activity extends Activity { private native void nativeGraphicsChanged(int width, int height); private native void nativeOnKeyDown(int keyCode, int metaState); private native void nativeOnKeyUp(int keyCode, int metaState); - private native void nativeIncreaseCPUSpeed(); - private native void nativeDecreaseCPUSpeed(); public native void nativeOnResume(boolean isSystemResume); public native void nativeOnPause(); @@ -173,18 +166,10 @@ public class Apple2Activity extends Activity { w = h; h = w_; } - if (mView.getHeight() - h > SOFTKEYBOARD_THRESHOLD) { - Log.d(TAG, "Soft keyboard appears to be occupying screen real estate ..."); - Apple2Activity.this.mSoftKeyboardShowing = true; - } else { - Log.d(TAG, "Soft keyboard appears to be gone ..."); - Apple2Activity.this.mSoftKeyboardShowing = false; - } + // I actually think this whole observer is now spurious ... but it's triggering SEGFAULT ... should investigate ... nativeGraphicsChanged(w, h); } }); - - mDetector = new GestureDetector(this, new Apple2GestureListener()); } @Override @@ -201,10 +186,6 @@ public class Apple2Activity extends Activity { Log.d(TAG, "onPause()"); mView.onPause(); - if (isSoftKeyboardShowing()) { - mView.toggleKeyboard(); - } - // Apparently not good to leave popup/dialog windows showing when backgrounding. // Dismiss these popups to avoid android.view.WindowLeaked issues Apple2MainMenu mainMenu = mView.getMainMenu(); @@ -333,74 +314,12 @@ public class Apple2Activity extends Activity { break; } - this.mDetector.onTouchEvent(event); + mainMenu.show(); } while (false); return super.onTouchEvent(event); } - private class Apple2GestureListener extends GestureDetector.SimpleOnGestureListener { - private static final String TAG = "Gestures"; - - @Override - public boolean onDown(MotionEvent event) { - Log.d(TAG,"onDown: " + event.toString()); - return true; - } - - @Override - public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { - if ((event1 == null) || (event2 == null)) { - return false; - } - if (mSwipeTogglesSpeed) { - float ev1X = event1.getX(); - float ev2X = event2.getX(); - if (ev1X < ev2X) { - nativeIncreaseCPUSpeed(); - } else { - nativeDecreaseCPUSpeed(); - } - } - return true; - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent event) { - if (event == null) { - return false; - } - Log.d(TAG, "onSingleTapConfirmed: " + event.toString()); - Apple2MainMenu mainMenu = Apple2Activity.this.mView.getMainMenu(); - if (mainMenu.isShowing()) { - Log.d(TAG, "dismissing main menu"); - mainMenu.dismiss(); - } else if (Apple2Activity.this.isSoftKeyboardShowing()) { - Log.d(TAG, "hiding keyboard"); - Apple2Activity.this.mView.toggleKeyboard(); - } else if (mSingleTapShowsMainMenu) { - Log.d(TAG, "showing main menu"); - Apple2Activity.this.mView.showMainMenu(); - } - return true; - } - - @Override - public boolean onDoubleTap(MotionEvent event) { - if (event == null) { - return false; - } - if (mDoubleTapShowsKeyboard) { - Log.d(TAG, "onDoubleTap: " + event.toString()); - if (!Apple2Activity.this.isSoftKeyboardShowing()) { - Log.d(TAG, "showing keyboard"); - Apple2Activity.this.mView.toggleKeyboard(); - } - } - return true; - } - } - public void graphicsInitialized(int w, int h) { if (w < h) { // assure landscape dimensions @@ -431,10 +350,6 @@ public class Apple2Activity extends Activity { return mHeight; } - public boolean isSoftKeyboardShowing() { - return mSoftKeyboardShowing; - } - public void maybeQuitApp() { nativeOnPause(); if (mQuitDialog == null) { @@ -487,14 +402,14 @@ public class Apple2Activity extends Activity { } public void setSwipeTogglesSpeed(boolean swipeTogglesSpeed) { - mSwipeTogglesSpeed = swipeTogglesSpeed; + //mSwipeTogglesSpeed = swipeTogglesSpeed; } public void setSingleTapShowsMainMenu(boolean singleTapShowsMainMenu) { - mSingleTapShowsMainMenu = singleTapShowsMainMenu; + //mSingleTapShowsMainMenu = singleTapShowsMainMenu; } public void setDoubleTapShowsKeyboard(boolean doubleTapShowsKeyboard) { - mDoubleTapShowsKeyboard = doubleTapShowsKeyboard; + //mDoubleTapShowsKeyboard = doubleTapShowsKeyboard; } } diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java index 0a3ad5e4..8e806877 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java @@ -112,11 +112,6 @@ class Apple2View extends GLSurfaceView { return (mMainMenu == null) ? null : mMainMenu.getDisksMenu(); } - public void toggleKeyboard() { - InputMethodManager inputMethodManager=(InputMethodManager)mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.toggleSoftInputFromWindow(getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); - } - private static class ContextFactory implements GLSurfaceView.EGLContextFactory { private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { diff --git a/Android/jni/jnihooks.c b/Android/jni/jnihooks.c index 07255c7e..02f0e210 100644 --- a/Android/jni/jnihooks.c +++ b/Android/jni/jnihooks.c @@ -13,7 +13,6 @@ #include "androidkeys.h" #include -#include enum { ANDROID_ACTION_DOWN = 0x0, @@ -25,6 +24,7 @@ enum { }; static bool nativePaused = false; +static bool nativeRequestsShowMainMenu = false; #if TESTING static bool _run_tests(void) { @@ -74,6 +74,13 @@ static inline int _androidTouchEvent2JoystickEvent(jint action) { } } +static void _nativeRequestsShowMainMenu(void) { + nativeRequestsShowMainMenu = true; +} + +// ---------------------------------------------------------------------------- +// JNI functions + void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobject obj, jstring j_dataDir) { const char *dataDir = (*env)->GetStringUTFChars(env, j_dataDir, 0); @@ -118,7 +125,9 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnResume(JNIEnv *env, jobje } LOG("%s", "native onResume..."); if (isSystemResume) { - // TODO POSSIBLY : message showing paused state + if (video_backend->animation_showPaused) { + video_backend->animation_showPaused(); + } } else { nativePaused = false; pthread_mutex_unlock(&interface_mutex); @@ -215,73 +224,16 @@ jboolean Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnTouch(JNIEnv *env, jo //} bool consumed = interface_onTouchEvent(joyaction, pointerCount, pointerIndex, x_coords, y_coords); + if (nativeRequestsShowMainMenu) { + nativeRequestsShowMainMenu = false; + consumed = false; + } (*env)->ReleaseFloatArrayElements(env, xCoords, x_coords, 0); (*env)->ReleaseFloatArrayElements(env, yCoords, y_coords, 0); return consumed; } -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeIncreaseCPUSpeed(JNIEnv *env, jobject obj) { - pthread_mutex_lock(&interface_mutex); - - int percent_scale = (int)round(cpu_scale_factor * 100.0); - if (percent_scale >= 100) { - percent_scale += 25; - } else { - percent_scale += 5; - } - cpu_scale_factor = percent_scale/100.0; - - if (cpu_scale_factor > CPU_SCALE_FASTEST) { - cpu_scale_factor = CPU_SCALE_FASTEST; - } - - LOG("native set emulation percentage to %f", cpu_scale_factor); - - if (video_backend->animation_showCPUSpeed) { - video_backend->animation_showCPUSpeed(); - } - -#warning HACK TODO FIXME ... refactor timing stuff - timing_toggle_cpu_speed(); - timing_toggle_cpu_speed(); - - pthread_mutex_unlock(&interface_mutex); -} - -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeDecreaseCPUSpeed(JNIEnv *env, jobject obj) { - pthread_mutex_lock(&interface_mutex); - - int percent_scale = (int)round(cpu_scale_factor * 100.0); - if (cpu_scale_factor == CPU_SCALE_FASTEST) { - cpu_scale_factor = CPU_SCALE_FASTEST0; - percent_scale = (int)round(cpu_scale_factor * 100); - } else { - if (percent_scale > 100) { - percent_scale -= 25; - } else { - percent_scale -= 5; - } - } - cpu_scale_factor = percent_scale/100.0; - - if (cpu_scale_factor < CPU_SCALE_SLOWEST) { - cpu_scale_factor = CPU_SCALE_SLOWEST; - } - - LOG("native set emulation percentage to %f", cpu_scale_factor); - - if (video_backend->animation_showCPUSpeed) { - video_backend->animation_showCPUSpeed(); - } - -#warning HACK TODO FIXME ... refactor timing stuff - timing_toggle_cpu_speed(); - timing_toggle_cpu_speed(); - - pthread_mutex_unlock(&interface_mutex); -} - void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSetColor(JNIEnv *env, jobject obj, jint color) { LOG("native set color : %d", color); if (color < COLOR_NONE || color > COLOR_INTERP) { @@ -318,3 +270,11 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeChooseDisk(JNIEnv *env, job (*env)->ReleaseStringUTFChars(env, jPath, path); } +// ---------------------------------------------------------------------------- +// Constructor + +__attribute__((constructor(CTOR_PRIORITY_LATE))) +static void _init_jnihooks(void) { + video_backend->hostenv_showMainMenu = &_nativeRequestsShowMainMenu; +} + diff --git a/Android/jni/sources.mk b/Android/jni/sources.mk index 7093b91f..45284bb6 100644 --- a/Android/jni/sources.mk +++ b/Android/jni/sources.mk @@ -16,6 +16,7 @@ APPLE2_VIDEO_SRC = \ $(APPLE2_SRC_PATH)/video/glalert.c \ $(APPLE2_SRC_PATH)/video/gltouchjoy.c \ $(APPLE2_SRC_PATH)/video/gltouchkbd.c \ + $(APPLE2_SRC_PATH)/video/gltouchmenu.c \ $(APPLE2_SRC_PATH)/video_util/matrixUtil.c \ $(APPLE2_SRC_PATH)/video_util/modelUtil.c \ $(APPLE2_SRC_PATH)/video_util/sourceUtil.c \ diff --git a/src/font.c b/src/font.c index 8a24802b..10ad7265 100644 --- a/src/font.c +++ b/src/font.c @@ -307,10 +307,10 @@ const unsigned char interface_glyphs[256] = 0x1c, 0x24, 0x04, 0x3e, 0x22, 0x22, 0x2a, 0x3e, /* : 0x10 ----------------------- reverse return arrow */ 0x01, 0x01, 0x01, 0x11, 0x31, 0x7f, 0x30, 0x10, - /* : 0x11 */ -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* : 0x12 */ -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* : 0x11 ----------------------- glyph_sprouting_menu */ +0x7f, 0x41, 0x49, 0x5d, 0x49, 0x41, 0x7f, 0x00, + /* : 0x12 ----------------------- glyph_joystick */ +0x10, 0x08, 0x1e, 0x22, 0x22, 0x2a, 0x22, 0x3e, /* : 0x13 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* : 0x14 ----------------------- glyph_lowercase */ diff --git a/src/font.txt b/src/font.txt index d1664dc2..3a83c748 100644 --- a/src/font.txt +++ b/src/font.txt @@ -1327,24 +1327,24 @@ ####### ....##. ....#.. -: 0x11 -....... -....... -....... -....... -....... -....... -....... -....... -: 0x12 -....... -....... -....... -....... -....... -....... -....... +: 0x11 ----------------------- glyph_sprouting_menu +####### +#.....# +#..#..# +#.###.# +#..#..# +#.....# +####### ....... +: 0x12 ----------------------- glyph_joystick +....#.. +...#... +.####.. +.#...#. +.#...#. +.#.#.#. +.#...#. +.#####. : 0x13 ....... ....... diff --git a/src/interface.c b/src/interface.c index 7052ca00..b660e831 100644 --- a/src/interface.c +++ b/src/interface.c @@ -19,6 +19,8 @@ #if INTERFACE_TOUCH // touch interface managed elsewhere bool (*interface_onTouchEvent)(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) = NULL; +bool (*interface_isTouchMenuAvailable)(void) = NULL; +void (*interface_setTouchMenuEnabled)(bool enabled) = NULL; #endif // 2015/04/12 : This was legacy code for rendering the menu interfaces on desktop Linux. Portions here are resurrected diff --git a/src/interface.h b/src/interface.h index c1a82898..c5c66037 100644 --- a/src/interface.h +++ b/src/interface.h @@ -60,6 +60,12 @@ typedef enum interface_touch_event_t { // handle touch event extern bool (*interface_onTouchEvent)(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords); + +// is the touch menu module itself available? +extern bool (*interface_isTouchMenuAvailable)(void); + +// enable/disable touch menu HUD element +extern void (*interface_setTouchMenuEnabled)(bool enabled); #endif diff --git a/src/video/Basic.fsh b/src/video/Basic.fsh index 2d19358b..a464acf0 100644 --- a/src/video/Basic.fsh +++ b/src/video/Basic.fsh @@ -39,6 +39,10 @@ uniform sampler2D buttonTexture; // Keyboard uniform sampler2D kbdTexture; +// HUD Sprouting Menus +uniform sampler2D menuLeftTexture; +uniform sampler2D menuRightTexture; + #if __VERSION__ >= 140 #define OUTPUT_TEXTURE(TEX) \ vec4 tex = texture(TEX, varTexcoord.st, 0.0); \ @@ -62,6 +66,10 @@ void main(void) OUTPUT_TEXTURE(buttonTexture); } else if (tex2Use == 4) { OUTPUT_TEXTURE(kbdTexture); + } else if (tex2Use == 5) { + OUTPUT_TEXTURE(menuLeftTexture); + } else if (tex2Use == 6) { + OUTPUT_TEXTURE(menuRightTexture); } else { //OUTPUT_RED(); -- WTF is this failing? } diff --git a/src/video/gltouchmenu.c b/src/video/gltouchmenu.c new file mode 100644 index 00000000..8818fec9 --- /dev/null +++ b/src/video/gltouchmenu.c @@ -0,0 +1,678 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +#include "common.h" +#include "video/glvideo.h" +#include "video/glhudmodel.h" +#include "video/glnode.h" + +#if !INTERFACE_TOUCH +#error this is a touch interface module, possibly you mean to not compile this at all? +#endif + +#define MODEL_DEPTH -1/32.f + +#define MENU_TEMPLATE_COLS 2 +#define MENU_TEMPLATE_ROWS 2 + +// HACK NOTE FIXME TODO : interpolated pixel adjustment still necessary ... +#define MENU_FB_WIDTH ((MENU_TEMPLATE_COLS * FONT80_WIDTH_PIXELS) + INTERPOLATED_PIXEL_ADJUSTMENT) +#define MENU_FB_HEIGHT (MENU_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS) + +#define MENU_OBJ_W 1/2.f +#define MENU_OBJ_H 1/2.f + +HUD_CLASS(GLModelHUDMenu, + char *pixelsAlt; // alternate color pixels +); + +static bool isAvailable = false; // Were there any OpenGL/memory errors on initialization? +static bool isEnabled = true; // Does player want this enabled? +static float minAlpha = 1/4.f; // Minimum alpha value of components (at zero, will not render) + +static char topLeftTemplateHidden[MENU_TEMPLATE_ROWS][MENU_TEMPLATE_COLS+1] = { + "++", + "++", +}; + +static char topLeftTemplateShowing[MENU_TEMPLATE_ROWS][MENU_TEMPLATE_COLS+1] = { + "++", + "++", +}; + +static char topRightTemplateHidden[MENU_TEMPLATE_ROWS][MENU_TEMPLATE_COLS+1] = { + "++", + "++", +}; + +static char topRightTemplateShowing[MENU_TEMPLATE_ROWS][MENU_TEMPLATE_COLS+1] = { + "++", + "++", +}; + +// touch viewport + +static struct { + int width; + int height; + + // top left hitbox + int topLeftX; + int topLeftXHalf; + int topLeftXMax; + int topLeftY; + int topLeftYHalf; + int topLeftYMax; + + // top right hitbox + int topRightX; + int topRightXHalf; + int topRightXMax; + int topRightY; + int topRightYHalf; + int topRightYMax; + +} touchport = { 0 }; + +// touch menu variables + +static struct { + GLModel *model; + bool modelDirty; // TODO : movement animation + bool isShowing; +} hudTopLeft = { 0 }; + +static struct { + GLModel *model; + bool modelDirty; // TODO : movement animation + bool isShowing; +} hudTopRight = { 0 }; + +struct timespec timingBegin = { 0 }; + +// ---------------------------------------------------------------------------- + +static inline void _present_menu(GLModel *parent, char *template) { + GLModelHUDMenu *hudMenu = (GLModelHUDMenu *)parent->custom; + memcpy(hudMenu->tpl, template, sizeof(topLeftTemplateHidden/* assuming all templates the same size */)); + + // setup the alternate color (AKA selected) pixels + hudMenu->colorScheme = GREEN_ON_BLACK; + glhud_setupDefault(parent); + memcpy(hudMenu->pixelsAlt, hudMenu->pixels, (hudMenu->pixWidth * hudMenu->pixHeight)); + + // setup normal color pixels + hudMenu->colorScheme = RED_ON_BLACK; + glhud_setupDefault(parent); +} + +static inline void _show_top_left(void) { + _present_menu(hudTopLeft.model, topLeftTemplateShowing[0]); + hudTopLeft.isShowing = true; +} + +static inline void _hide_top_left(void) { + _present_menu(hudTopLeft.model, topLeftTemplateHidden[0]); + hudTopLeft.isShowing = false; +} + +static inline void _show_top_right(void) { + _present_menu(hudTopRight.model, topRightTemplateShowing[0]); + hudTopRight.isShowing = true; +} + +static inline void _hide_top_right(void) { + _present_menu(hudTopRight.model, topRightTemplateHidden[0]); + hudTopRight.isShowing = false; +} + +static float _get_menu_visibility(void) { + struct timespec now = { 0 }; + struct timespec deltat = { 0 }; + float alpha = minAlpha; + + clock_gettime(CLOCK_MONOTONIC, &now); + alpha = minAlpha; + deltat = timespec_diff(timingBegin, now, NULL); + if (deltat.tv_sec == 0) { + alpha = 1.0; + if (deltat.tv_nsec >= NANOSECONDS_PER_SECOND/2) { + alpha -= ((float)deltat.tv_nsec-(NANOSECONDS_PER_SECOND/2)) / (float)(NANOSECONDS_PER_SECOND/2); + if (alpha < minAlpha) { + alpha = minAlpha; + } + } + } + + return alpha; +} + +static inline bool _is_point_on_left_menu(float x, float y) { + if (hudTopLeft.isShowing) { + return (x >= touchport.topLeftX && x <= touchport.topLeftXMax && y >= touchport.topLeftY && y <= touchport.topLeftYMax); + } else { + return (x >= touchport.topLeftX && x <= touchport.topLeftXHalf && y >= touchport.topLeftY && y <= touchport.topLeftYHalf); + } +} + +static inline bool _is_point_on_right_menu(float x, float y) { + if (hudTopRight.isShowing) { + return (x >= touchport.topRightX && x <= touchport.topRightXMax && y >= touchport.topRightY && y <= touchport.topRightYMax); + } else { + return (x >= touchport.topRightXHalf && x <= touchport.topRightXMax && y >= touchport.topRightY && y <= touchport.topRightYHalf); + } +} + +#warning FIXME TODO : make this function generic _screen_to_model() ? +static inline void _screen_to_menu(float x, float y, OUTPARM int *col, OUTPARM int *row, OUTPARM bool *isTopLeft) { + + GLModelHUDMenu *hudMenu = (GLModelHUDMenu *)(/* assuming both have same width/height */hudTopLeft.model->custom); + + unsigned int keyW = (touchport.topLeftXMax - touchport.topLeftX) / (hudMenu->tplWidth+1/* interpolated adjustment HACK NOTE FIXME TODO */); + unsigned int keyH = (touchport.topLeftYMax - touchport.topLeftY) / (hudMenu->tplHeight); + const int xOff = (keyW * 0.5); // HACK NOTE FIXME TODO : interpolated pixel adjustment still necessary ... + + hudMenu = NULL; + if (x < touchport.width/2) { + *isTopLeft = true; + hudMenu = (GLModelHUDMenu *)hudTopLeft.model->custom; + *col = (x - (touchport.topLeftX+xOff)) / keyW; + *row = (y - touchport.topLeftY) / keyH; + LOG("SCREEN TO MENU : xOff:%d topLeftX:%d topLeftXMax:%d keyW:%d ... scrn:(%d,%d)->menu:(%d,%d)", xOff, touchport.topLeftX, touchport.topLeftXMax, keyW, (int)x, (int)y, *col, *row); + } else { + *isTopLeft = false; + hudMenu = (GLModelHUDMenu *)hudTopRight.model->custom; + *col = (x - (touchport.topRightX+xOff)) / keyW; + *row = (y - touchport.topRightY) / keyH; + LOG("SCREEN TO MENU : xOff:%d topRightX:%d topRightXMax:%d keyW:%d ... scrn:(%d,%d)->menu:(%d,%d)", xOff, touchport.topRightX, touchport.topRightXMax, keyW, (int)x, (int)y, *col, *row); + } + + if (*col < 0) { + *col = 0; + } /* interpolated adjustment HACK NOTE FIXME TODO */ else if (*col >= hudMenu->tplWidth) { + *col = hudMenu->tplWidth-1; + } + if (*row < 0) { + *row = 0; + } +} + +static void _increase_cpu_speed(void) { + pthread_mutex_lock(&interface_mutex); + + int percent_scale = (int)round(cpu_scale_factor * 100.0); + if (percent_scale >= 100) { + percent_scale += 25; + } else { + percent_scale += 5; + } + cpu_scale_factor = percent_scale/100.0; + + if (cpu_scale_factor > CPU_SCALE_FASTEST) { + cpu_scale_factor = CPU_SCALE_FASTEST; + } + + LOG("native set emulation percentage to %f", cpu_scale_factor); + + if (video_backend->animation_showCPUSpeed) { + video_backend->animation_showCPUSpeed(); + } + +#warning HACK TODO FIXME ... refactor timing stuff + timing_toggle_cpu_speed(); + timing_toggle_cpu_speed(); + + pthread_mutex_unlock(&interface_mutex); +} + +void _decrease_cpu_speed(void) { + pthread_mutex_lock(&interface_mutex); + + int percent_scale = (int)round(cpu_scale_factor * 100.0); + if (cpu_scale_factor == CPU_SCALE_FASTEST) { + cpu_scale_factor = CPU_SCALE_FASTEST0; + percent_scale = (int)round(cpu_scale_factor * 100); + } else { + if (percent_scale > 100) { + percent_scale -= 25; + } else { + percent_scale -= 5; + } + } + cpu_scale_factor = percent_scale/100.0; + + if (cpu_scale_factor < CPU_SCALE_SLOWEST) { + cpu_scale_factor = CPU_SCALE_SLOWEST; + } + + LOG("native set emulation percentage to %f", cpu_scale_factor); + + if (video_backend->animation_showCPUSpeed) { + video_backend->animation_showCPUSpeed(); + } + +#warning HACK TODO FIXME ... refactor timing stuff + timing_toggle_cpu_speed(); + timing_toggle_cpu_speed(); + + pthread_mutex_unlock(&interface_mutex); +} + +static inline bool _sprout_menu(float x, float y) { + + if (! (_is_point_on_left_menu(x, y) || _is_point_on_right_menu(x, y)) ) { + return false; + } + + bool isTopLeft = false; + int col = -1; + int row = -1; + + _screen_to_menu(x, y, &col, &row, &isTopLeft); + + if (isTopLeft) { + + // hide other + _hide_top_right(); + + // maybe show this one + if (!hudTopLeft.isShowing) { + if (col == 0 && row == 0) { + _show_top_left(); + } + } + + return hudTopLeft.isShowing; + } else { + + // hide other + _hide_top_left(); + + // maybe show this one + if (!hudTopRight.isShowing) { + if (col == 1 && row == 0) { + _show_top_right(); + } + } + return hudTopRight.isShowing; + } +} + +static inline bool _tap_menu_item(float x, float y) { + if (! (_is_point_on_left_menu(x, y) || _is_point_on_right_menu(x, y)) ) { + return false; + } + + bool isTopLeft = false; + int col = -1; + int row = -1; + + _screen_to_menu(x, y, &col, &row, &isTopLeft); + + int selectedItem = -1; + if (isTopLeft && hudTopLeft.isShowing) { + selectedItem = topLeftTemplateShowing[row][col]; + } else if (!isTopLeft && hudTopRight.isShowing) { + selectedItem = topRightTemplateShowing[row][col]; + } + + switch (selectedItem) { + + case MOUSETEXT_LEFT: + LOG("decreasing cpu speed..."); + _decrease_cpu_speed(); + break; + + case MOUSETEXT_RIGHT: + LOG("increasing cpu speed..."); + _increase_cpu_speed(); + break; + + case MOUSETEXT_CHECKMARK: + LOG("showing main menu..."); + if (video_backend->hostenv_showMainMenu) { + video_backend->hostenv_showMainMenu(); + } + _hide_top_right(); + break; + + case ICONTEXT_MENU_TOUCHJOY: + LOG("showing touch joystick ..."); + keydriver_setTouchKeyboardOwnsScreen(false); + if (video_backend->animation_hideTouchKeyboard) { + video_backend->animation_hideTouchKeyboard(); + } + joydriver_setTouchJoystickOwnsScreen(true); + if (video_backend->animation_showTouchJoystick) { + video_backend->animation_showTouchJoystick(); + } + _hide_top_left(); + break; + + case ICONTEXT_UPPERCASE: + LOG("showing touch keyboard ..."); + joydriver_setTouchJoystickOwnsScreen(false); + if (video_backend->animation_hideTouchJoystick) { + video_backend->animation_hideTouchJoystick(); + } + keydriver_setTouchKeyboardOwnsScreen(true); + if (video_backend->animation_showTouchKeyboard) { + video_backend->animation_showTouchKeyboard(); + } + _hide_top_left(); + break; + + case ICONTEXT_MENU_SPROUT: + LOG("sprout ..."); + break; + + case ICONTEXT_NONACTIONABLE: + default: + LOG("nonactionable ..."); + _hide_top_left(); + _hide_top_right(); + break; + } + + return true; +} + +// ---------------------------------------------------------------------------- +// GLCustom functions + +static void _setup_touchmenu_top_left(GLModel *parent) { + GLModelHUDMenu *hudMenu = (GLModelHUDMenu *)parent->custom; + + hudMenu->tplWidth = MENU_TEMPLATE_COLS; + hudMenu->tplHeight = MENU_TEMPLATE_ROWS; + hudMenu->pixWidth = MENU_FB_WIDTH; + hudMenu->pixHeight = MENU_FB_HEIGHT; + + topLeftTemplateHidden[0][0] = ICONTEXT_MENU_SPROUT; + topLeftTemplateHidden[0][1] = ICONTEXT_NONACTIONABLE; + topLeftTemplateHidden[1][0] = ICONTEXT_NONACTIONABLE; + topLeftTemplateHidden[1][1] = ICONTEXT_NONACTIONABLE; + + topLeftTemplateShowing[0][0] = ICONTEXT_MENU_SPROUT; + topLeftTemplateShowing[0][1] = MOUSETEXT_RIGHT; + topLeftTemplateShowing[1][1] = ICONTEXT_UPPERCASE; + topLeftTemplateShowing[1][0] = ICONTEXT_MENU_TOUCHJOY; + + const unsigned int size = sizeof(topLeftTemplateHidden); + hudMenu->tpl = calloc(size, 1); + hudMenu->pixels = calloc(MENU_FB_WIDTH * MENU_FB_HEIGHT, 1); + hudMenu->pixelsAlt = calloc(MENU_FB_WIDTH * MENU_FB_HEIGHT, 1); + + _present_menu(parent, topLeftTemplateHidden[0]); +} + +static void _setup_touchmenu_top_right(GLModel *parent) { + GLModelHUDMenu *hudMenu = (GLModelHUDMenu *)parent->custom; + + hudMenu->tplWidth = MENU_TEMPLATE_COLS; + hudMenu->tplHeight = MENU_TEMPLATE_ROWS; + hudMenu->pixWidth = MENU_FB_WIDTH; + hudMenu->pixHeight = MENU_FB_HEIGHT; + + topRightTemplateHidden[0][0] = ICONTEXT_NONACTIONABLE; + topRightTemplateHidden[0][1] = ICONTEXT_MENU_SPROUT; + topRightTemplateHidden[1][0] = ICONTEXT_NONACTIONABLE; + topRightTemplateHidden[1][1] = ICONTEXT_NONACTIONABLE; + + topRightTemplateShowing[0][0] = MOUSETEXT_LEFT; + topRightTemplateShowing[0][1] = ICONTEXT_MENU_SPROUT; + topRightTemplateShowing[1][0] = ICONTEXT_NONACTIONABLE; + topRightTemplateShowing[1][1] = MOUSETEXT_CHECKMARK; + + const unsigned int size = sizeof(topRightTemplateHidden); + hudMenu->tpl = calloc(size, 1); + hudMenu->pixels = calloc(MENU_FB_WIDTH * MENU_FB_HEIGHT, 1); + hudMenu->pixelsAlt = calloc(MENU_FB_WIDTH * MENU_FB_HEIGHT, 1); + + _present_menu(parent, topRightTemplateHidden[0]); +} + +static void *_create_touchmenu(void) { + GLModelHUDMenu *hudMenu = (GLModelHUDMenu *)calloc(sizeof(GLModelHUDMenu), 1); + if (hudMenu) { + hudMenu->blackIsTransparent = true; + } + return hudMenu; +} + +static void _destroy_touchmenu(GLModel *parent) { + GLModelHUDMenu *hudMenu = (GLModelHUDMenu *)parent->custom; + if (!hudMenu) { + return; + } + FREE(hudMenu->pixelsAlt); + glhud_destroyDefault(parent); +} + +// ---------------------------------------------------------------------------- +// GLNode functions + +static void gltouchmenu_setup(void) { + LOG("gltouchmenu_setup ..."); + + mdlDestroyModel(&hudTopLeft.model); + hudTopLeft.model = mdlCreateQuad(-1.0, 1.0-MENU_OBJ_H, MENU_OBJ_W, MENU_OBJ_H, MODEL_DEPTH, MENU_FB_WIDTH, MENU_FB_HEIGHT, GL_RGBA/*RGBA_8888*/, (GLCustom){ + .create = &_create_touchmenu, + .setup = &_setup_touchmenu_top_left, + .destroy = &_destroy_touchmenu, + }); + if (!hudTopLeft.model) { + LOG("gltouchmenu initialization problem"); + return; + } + if (!hudTopLeft.model->custom) { + LOG("gltouchmenu HUD initialization problem"); + return; + } + + mdlDestroyModel(&hudTopRight.model); + hudTopRight.model = mdlCreateQuad(1.0-MENU_OBJ_W, 1.0-MENU_OBJ_H, MENU_OBJ_W, MENU_OBJ_H, MODEL_DEPTH, MENU_FB_WIDTH, MENU_FB_HEIGHT, GL_RGBA/*RGBA_8888*/, (GLCustom){ + .create = &_create_touchmenu, + .setup = &_setup_touchmenu_top_right, + .destroy = &_destroy_touchmenu, + }); + if (!hudTopRight.model) { + LOG("gltouchmenu initialization problem"); + return; + } + if (!hudTopRight.model->custom) { + LOG("gltouchmenu HUD initialization problem"); + return; + } + + clock_gettime(CLOCK_MONOTONIC, &timingBegin); + + isAvailable = true; + + GL_ERRLOG("gltouchmenu_setup"); +} + +static void gltouchmenu_shutdown(void) { + LOG("gltouchmenu_shutdown ..."); + if (!isAvailable) { + return; + } + + isAvailable = false; + + mdlDestroyModel(&hudTopLeft.model); + mdlDestroyModel(&hudTopRight.model); +} + +static void gltouchmenu_render(void) { + if (!isAvailable) { + return; + } + if (!isEnabled) { + return; + } + + float alpha = _get_menu_visibility(); + if (alpha <= 0.0) { + return; + } + + glViewport(0, 0, touchport.width, touchport.height); // NOTE : show these HUD elements beyond the A2 framebuffer dimensions + glUniform1f(alphaValue, alpha); + + // render top left sprouting menu + + glActiveTexture(TEXTURE_ACTIVE_TOUCHMENU_LEFT); + glBindTexture(GL_TEXTURE_2D, hudTopLeft.model->textureName); + if (hudTopLeft.model->texDirty) { + hudTopLeft.model->texDirty = false; + glTexImage2D(GL_TEXTURE_2D, /*level*/0, /*internal format*/GL_RGBA, hudTopLeft.model->texWidth, hudTopLeft.model->texHeight, /*border*/0, /*format*/GL_RGBA, GL_UNSIGNED_BYTE, hudTopLeft.model->texPixels); + } + if (hudTopLeft.modelDirty) { + hudTopLeft.modelDirty = false; + glBindBuffer(GL_ARRAY_BUFFER, hudTopLeft.model->posBufferName); + glBufferData(GL_ARRAY_BUFFER, hudTopLeft.model->positionArraySize, hudTopLeft.model->positions, GL_DYNAMIC_DRAW); + } + glUniform1i(uniformTex2Use, TEXTURE_ID_TOUCHMENU_LEFT); + glhud_renderDefault(hudTopLeft.model); + + // render top right sprouting menu + + glActiveTexture(TEXTURE_ACTIVE_TOUCHMENU_RIGHT); + glBindTexture(GL_TEXTURE_2D, hudTopRight.model->textureName); + if (hudTopRight.model->texDirty) { + hudTopRight.model->texDirty = false; + glTexImage2D(GL_TEXTURE_2D, /*level*/0, /*internal format*/GL_RGBA, hudTopRight.model->texWidth, hudTopRight.model->texHeight, /*border*/0, /*format*/GL_RGBA, GL_UNSIGNED_BYTE, hudTopRight.model->texPixels); + } + if (hudTopRight.modelDirty) { + hudTopRight.modelDirty = false; + glBindBuffer(GL_ARRAY_BUFFER, hudTopRight.model->posBufferName); + glBufferData(GL_ARRAY_BUFFER, hudTopRight.model->positionArraySize, hudTopRight.model->positions, GL_DYNAMIC_DRAW); + } + glUniform1i(uniformTex2Use, TEXTURE_ID_TOUCHMENU_RIGHT); + glhud_renderDefault(hudTopRight.model); + + GL_ERRLOG("gltouchmenu_render"); +} + +static void gltouchmenu_reshape(int w, int h) { + LOG("gltouchmenu_reshape(%d, %d)", w, h); + + touchport.topLeftX = 0; + touchport.topLeftY = 0; + touchport.topRightY = 0; + + if (w > touchport.width) { + const int menuPixelW = w * (MENU_OBJ_W/2.f); + touchport.width = w; + touchport.topLeftXHalf = menuPixelW/2; + touchport.topLeftXMax = menuPixelW; + touchport.topRightX = w - menuPixelW; + touchport.topRightXHalf = w - (menuPixelW/2); + touchport.topRightXMax = w; + } + if (h > touchport.height) { + const int menuPixelH = h * (MENU_OBJ_H/2.f); + touchport.height = h; + touchport.topLeftYHalf = menuPixelH/2; + touchport.topLeftYMax = menuPixelH; + touchport.topRightYHalf = menuPixelH/2; + touchport.topRightYMax = menuPixelH; + } +} + +static bool gltouchmenu_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) { + + if (!isAvailable) { + return false; + } + if (!isEnabled) { + return false; + } + + LOG("gltouchmenu_onTouchEvent ..."); + + float x = x_coords[pointer_idx]; + float y = y_coords[pointer_idx]; + + bool handled = (_is_point_on_left_menu(x, y) || _is_point_on_right_menu(x, y)); + + switch (action) { + case TOUCH_DOWN: + case TOUCH_POINTER_DOWN: + _sprout_menu(x, y); + break; + + case TOUCH_MOVE: + break; + + case TOUCH_UP: + case TOUCH_POINTER_UP: + _tap_menu_item(x, y); + break; + + case TOUCH_CANCEL: + LOG("---MENU TOUCH CANCEL"); + break; + + default: + LOG("!!!MENU UNKNOWN TOUCH EVENT : %d", action); + break; + } + + if (handled) { + clock_gettime(CLOCK_MONOTONIC, &timingBegin); + } + + return handled; +} + +// ---------------------------------------------------------------------------- +// Animation and settings handling + +static bool gltouchmenu_isTouchMenuAvailable(void) { + return isAvailable; +} + +static void gltouchmenu_setTouchMenuEnabled(bool enabled) { + isEnabled = enabled; +} + +static void _animation_showTouchMenu(void) { + clock_gettime(CLOCK_MONOTONIC, &timingBegin); +} + +static void _animation_hideTouchMenu(void) { + timingBegin = (struct timespec){ 0 }; +} + +// ---------------------------------------------------------------------------- +// Constructor + +__attribute__((constructor(CTOR_PRIORITY_LATE))) +static void _init_gltouchmenu(void) { + LOG("Registering OpenGL software touch menu"); + + video_backend->animation_showTouchMenu = &_animation_showTouchMenu; + video_backend->animation_hideTouchMenu = &_animation_hideTouchMenu; + + interface_isTouchMenuAvailable = &gltouchmenu_isTouchMenuAvailable; + interface_setTouchMenuEnabled = &gltouchmenu_setTouchMenuEnabled; + + glnode_registerNode(RENDER_TOP, (GLNode){ + .setup = &gltouchmenu_setup, + .shutdown = &gltouchmenu_shutdown, + .render = &gltouchmenu_render, + .reshape = &gltouchmenu_reshape, + .onTouchEvent = &gltouchmenu_onTouchEvent, + }); +} + diff --git a/src/video/glvideo.c b/src/video/glvideo.c index b972753d..ae6a08d2 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -447,6 +447,16 @@ static GLuint _build_program(demoSource *vertexSource, demoSource *fragmentSourc glUniform1i(messageSamplerLoc, TEXTURE_ID_MESSAGE); } + GLint maxTextureUnits = -1; + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits); + + if (maxTextureUnits < TEXTURE_ID_MAX) { +#warning FIXME TODO ... gracefully handle devices with low max texture units? + ERRLOG("OOPS ... MAX TEXTURE UNITS : %d (<%d)", maxTextureUnits, TEXTURE_ID_MAX); + } else { + LOG("GL_MAX_TEXTURE_IMAGE_UNITS : %d", maxTextureUnits); + } + #if INTERFACE_TOUCH GLint axisSamplerLoc = glGetUniformLocation(prgName, "axisTexture"); if (axisSamplerLoc < 0) { @@ -468,6 +478,20 @@ static GLuint _build_program(demoSource *vertexSource, demoSource *fragmentSourc } else { glUniform1i(kbdSamplerLoc, TEXTURE_ID_TOUCHKBD); } + + GLint menuLeftSamplerLoc = glGetUniformLocation(prgName, "menuLeftTexture"); + if (menuLeftSamplerLoc < 0) { + LOG("OOPS, no menuLeftSamplerLoc shader : %d", menuLeftSamplerLoc); + } else { + glUniform1i(menuLeftSamplerLoc, TEXTURE_ID_TOUCHMENU_LEFT); + } + + GLint menuRightSamplerLoc = glGetUniformLocation(prgName, "menuRightTexture"); + if (menuRightSamplerLoc < 0) { + LOG("OOPS, no menuRightSamplerLoc shader : %d", menuRightSamplerLoc); + } else { + glUniform1i(menuRightSamplerLoc, TEXTURE_ID_TOUCHMENU_RIGHT); + } #endif uniformMVPIdx = glGetUniformLocation(prgName, "modelViewProjectionMatrix"); diff --git a/src/video/glvideo.h b/src/video/glvideo.h index 68c4220f..c5d89602 100644 --- a/src/video/glvideo.h +++ b/src/video/glvideo.h @@ -26,7 +26,10 @@ enum { TEXTURE_ID_TOUCHJOY_AXIS, TEXTURE_ID_TOUCHJOY_BUTTON, TEXTURE_ID_TOUCHKBD, + TEXTURE_ID_TOUCHMENU_LEFT, + TEXTURE_ID_TOUCHMENU_RIGHT, #endif + TEXTURE_ID_MAX, }; enum { @@ -36,7 +39,10 @@ enum { TEXTURE_ACTIVE_TOUCHJOY_AXIS = GL_TEXTURE2, TEXTURE_ACTIVE_TOUCHJOY_BUTTON = GL_TEXTURE3, TEXTURE_ACTIVE_TOUCHKBD = GL_TEXTURE4, + TEXTURE_ACTIVE_TOUCHMENU_LEFT = GL_TEXTURE5, + TEXTURE_ACTIVE_TOUCHMENU_RIGHT = GL_TEXTURE6, #endif + TEXTURE_ACTIVE_MAX, }; extern GLint uniformTex2Use; diff --git a/src/video/video.h b/src/video/video.h index 9ca7471a..b2eb4d57 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -26,14 +26,22 @@ typedef struct video_backend_s { void (*render)(void); void (*shutdown)(void); - // optional functions + // touch HUD functions + void (*hostenv_showMainMenu)(void); void (*animation_showTouchKeyboard)(void); void (*animation_hideTouchKeyboard)(void); + void (*animation_showTouchJoystick)(void); + void (*animation_hideTouchJoystick)(void); + void (*animation_showTouchMenu)(void); + void (*animation_hideTouchMenu)(void); + + // misc animations void (*animation_showMessage)(char *message, unsigned int cols, unsigned int rows); void (*animation_showPaused)(void); void (*animation_showCPUSpeed)(void); void (*animation_showDiskChosen)(int drive); void (*animation_showTrackSector)(int drive, int track, int sect); + } video_backend_s; /* @@ -175,6 +183,7 @@ uint8_t floating_bus_hibit(const bool hibit); #define MOUSETEXT_OPENAPPLE (MOUSETEXT_BEGIN+0x01) #define MOUSETEXT_CLOSEDAPPLE (MOUSETEXT_BEGIN+0x00) #define MOUSETEXT_HOURGLASS (MOUSETEXT_BEGIN+0x03) +#define MOUSETEXT_CHECKMARK (MOUSETEXT_BEGIN+0x04) #define ICONTEXT_BEGIN 0xA0 // offset + 0x20 length #define ICONTEXT_MENU_BEGIN ICONTEXT_BEGIN @@ -187,6 +196,9 @@ uint8_t floating_bus_hibit(const bool hibit); #define ICONTEXT_UNLOCK (ICONTEXT_BEGIN+0x0F) #define ICONTEXT_GOTO (ICONTEXT_BEGIN+0x10) +#define ICONTEXT_MENU_SPROUT (ICONTEXT_BEGIN+0x11) +#define ICONTEXT_MENU_TOUCHJOY (ICONTEXT_BEGIN+0x12) + #define ICONTEXT_KBD_BEGIN (ICONTEXT_BEGIN+0x14) #define ICONTEXT_LOWERCASE (ICONTEXT_KBD_BEGIN+0x00) #define ICONTEXT_UPPERCASE (ICONTEXT_KBD_BEGIN+0x01)