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
This commit is contained in:
Aaron Culliney 2015-04-26 18:40:05 -07:00
parent 1126a319ec
commit 68011fa8f2
13 changed files with 787 additions and 180 deletions

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -13,7 +13,6 @@
#include "androidkeys.h"
#include <jni.h>
#include <math.h>
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;
}

View File

@ -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 \

View File

@ -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 */

View File

@ -1327,24 +1327,24 @@
#######
....##.
....#..
: 0x11
.......
.......
.......
.......
.......
.......
.......
.......
: 0x12
.......
.......
.......
.......
.......
.......
.......
: 0x11 ----------------------- glyph_sprouting_menu
#######
#.....#
#..#..#
#.###.#
#..#..#
#.....#
#######
.......
: 0x12 ----------------------- glyph_joystick
....#..
...#...
.####..
.#...#.
.#...#.
.#.#.#.
.#...#.
.#####.
: 0x13
.......
.......

View File

@ -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

View File

@ -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

View File

@ -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?
}

678
src/video/gltouchmenu.c Normal file
View File

@ -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,
});
}

View File

@ -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");

View File

@ -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;

View File

@ -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)