apple2ix/src/video/gltouchjoy.c

904 lines
28 KiB
C
Raw Normal View History

2015-02-18 04:23:06 +00:00
/*
* 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 "video/gltouchjoy.h"
#if !INTERFACE_TOUCH
#error this is a touch interface module, possibly you mean to not compile this at all?
#endif
2015-02-18 04:23:06 +00:00
2015-07-26 17:41:12 +00:00
#define DEBUG_TOUCH_JOY 0
#if DEBUG_TOUCH_JOY
# define TOUCH_JOY_LOG(...) LOG(__VA_ARGS__)
#else
# define TOUCH_JOY_LOG(...)
#endif
#define MODEL_DEPTH -1/32.f
#define AXIS_TEMPLATE_COLS 5
#define AXIS_TEMPLATE_ROWS 5
#define BUTTON_TEMPLATE_COLS 1
#define BUTTON_TEMPLATE_ROWS 1
#define AXIS_FB_WIDTH (AXIS_TEMPLATE_COLS * FONT80_WIDTH_PIXELS)
#define AXIS_FB_HEIGHT (AXIS_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS)
2015-04-02 02:59:38 +00:00
#define BUTTON_FB_WIDTH (BUTTON_TEMPLATE_COLS * FONT80_WIDTH_PIXELS)
#define BUTTON_FB_HEIGHT (BUTTON_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS)
#define AXIS_OBJ_W 0.4
#define AXIS_OBJ_H 0.5
#define AXIS_OBJ_HALF_W (AXIS_OBJ_W/2.f)
#define AXIS_OBJ_HALF_H (AXIS_OBJ_H/2.f)
#define BUTTON_OBJ_W 0.2
#define BUTTON_OBJ_H 0.25
#define BUTTON_OBJ_HALF_W (BUTTON_OBJ_W/2.f)
#define BUTTON_OBJ_HALF_H (BUTTON_OBJ_H/2.f)
#define BUTTON_SWITCH_THRESHOLD_DEFAULT 22
#define BUTTON_TAP_DELAY_NANOS_DEFAULT 50000000
2015-04-02 02:59:38 +00:00
GLTouchJoyGlobals joyglobals = { 0 };
GLTouchJoyAxes axes = { 0 };
GLTouchJoyButtons buttons = { 0 };
static struct {
GLTouchJoyVariant *joys;
GLTouchJoyVariant *kpad;
GLTouchJoyVariant *curr;
} variant = { 0 };
2015-04-02 02:59:38 +00:00
// viewport touch
static struct {
int width;
int height;
2015-04-02 02:59:38 +00:00
// Axis box
int axisX;
int axisXMax;
int axisY;
int axisYMax;
// Button box
int buttonX;
int buttonXMax;
int buttonY;
int buttonYMax;
// TODO FIXME : support 2-players!
} touchport = { 0 };
// ----------------------------------------------------------------------------
2015-04-02 02:59:38 +00:00
#warning FIXME TODO ... this can become a common helper function ...
static inline float _get_component_visibility(struct timespec timingBegin) {
struct timespec now = { 0 };
struct timespec deltat = { 0 };
clock_gettime(CLOCK_MONOTONIC, &now);
float alpha = joyglobals.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 < joyglobals.minAlpha) {
alpha = joyglobals.minAlpha;
}
}
}
return alpha;
}
static void _setup_axis_object(GLModel *parent) {
if (UNLIKELY(!parent)) {
LOG("gltouchjoy WARN : cannot setup axis object without parent");
return;
}
GLModelHUDElement *hudElement = (GLModelHUDElement *)parent->custom;
if (hudElement->tpl == NULL) {
// deferred construction ...
const char axisTemplate[AXIS_TEMPLATE_ROWS][AXIS_TEMPLATE_COLS+1] = {
" @ ",
" | ",
"@-+-@",
" | ",
" @ ",
};
const unsigned int size = sizeof(axisTemplate);
hudElement->tpl = calloc(size, 1);
hudElement->tplWidth = AXIS_TEMPLATE_COLS;
hudElement->tplHeight = AXIS_TEMPLATE_ROWS;
memcpy(hudElement->tpl, axisTemplate, size);
hudElement->pixels = calloc(AXIS_FB_WIDTH * AXIS_FB_HEIGHT, 1);
hudElement->pixWidth = AXIS_FB_WIDTH;
hudElement->pixHeight = AXIS_FB_HEIGHT;
2015-04-02 02:59:38 +00:00
}
const unsigned int row = (AXIS_TEMPLATE_COLS+1);
for (unsigned int i=0; i<ROSETTE_ROWS; i++) {
for (unsigned int j=0; j<ROSETTE_COLS; j++) {
((hudElement->tpl)+(row*i*2))[j*2] = axes.rosetteChars[(i*ROSETTE_ROWS)+j];
}
}
glhud_setupDefault(parent);
2015-04-02 02:59:38 +00:00
}
static void *_create_touchjoy_hud(void) {
GLModelHUDElement *hudElement = (GLModelHUDElement *)glhud_createDefault();
if (hudElement) {
hudElement->blackIsTransparent = true;
}
return hudElement;
2015-04-02 02:59:38 +00:00
}
static void _setup_button_object(GLModel *parent) {
if (UNLIKELY(!parent)) {
LOG("gltouchjoy WARN : cannot setup button object without parent");
return;
}
GLModelHUDElement *hudElement = (GLModelHUDElement *)parent->custom;
if (hudElement->tpl == NULL) {
// deferred construction ...
const char buttonTemplate[BUTTON_TEMPLATE_ROWS][BUTTON_TEMPLATE_COLS+1] = {
"@",
};
const unsigned int size = sizeof(buttonTemplate);
hudElement->tpl = calloc(size, 1);
hudElement->tplWidth = BUTTON_TEMPLATE_COLS;
hudElement->tplHeight = BUTTON_TEMPLATE_ROWS;
memcpy(hudElement->tpl, buttonTemplate, size);
hudElement->pixels = calloc(BUTTON_FB_WIDTH * BUTTON_FB_HEIGHT, 1);
hudElement->pixWidth = BUTTON_FB_WIDTH;
hudElement->pixHeight = BUTTON_FB_HEIGHT;
}
const unsigned int row = (BUTTON_TEMPLATE_COLS+1);
((hudElement->tpl)+(row*0))[0] = buttons.activeChar;
glhud_setupDefault(parent);
2015-04-02 02:59:38 +00:00
}
static inline void _setup_button_object_with_char(char newChar) {
if (buttons.activeChar != newChar) {
buttons.activeChar = newChar;
_setup_button_object(buttons.model);
}
}
// ----------------------------------------------------------------------------
// Tap Delay Thread : delays processing of touch-down so that a different joystick button/key can be fired
static inline void _signal_tap_delay(void) {
pthread_mutex_lock(&buttons.tapDelayMutex);
pthread_cond_signal(&buttons.tapDelayCond);
pthread_mutex_unlock(&buttons.tapDelayMutex);
}
static void *_button_tap_delayed_thread(void *dummyptr) {
LOG(">>> [DELAYEDTAP] thread start ...");
pthread_mutex_lock(&buttons.tapDelayMutex);
do {
pthread_cond_wait(&buttons.tapDelayCond, &buttons.tapDelayMutex);
2015-07-26 17:41:12 +00:00
TOUCH_JOY_LOG(">>> [DELAYEDTAP] begin ...");
if (UNLIKELY(joyglobals.isShuttingDown)) {
break;
}
struct timespec ts = { .tv_sec=0, .tv_nsec=buttons.tapDelayNanos };
// sleep for the configured delay time
pthread_mutex_unlock(&buttons.tapDelayMutex);
nanosleep(&ts, NULL);
pthread_mutex_lock(&buttons.tapDelayMutex);
// wait until touch up/cancel
do {
// now set the emulator's joystick button values (or keypad value)
uint8_t displayChar = variant.curr->buttonPress();
_setup_button_object_with_char(displayChar);
if ( (buttons.trackingIndex == TOUCH_NONE) || joyglobals.isShuttingDown) {
break;
}
pthread_cond_wait(&buttons.tapDelayCond, &buttons.tapDelayMutex);
if ( (buttons.trackingIndex == TOUCH_NONE) || joyglobals.isShuttingDown) {
break;
}
2015-07-26 17:41:12 +00:00
TOUCH_JOY_LOG(">>> [DELAYEDTAP] looping ...");
} while (1);
if (UNLIKELY(joyglobals.isShuttingDown)) {
break;
}
// delay the ending of button tap or touch/move event by the configured delay time
pthread_mutex_unlock(&buttons.tapDelayMutex);
nanosleep(&ts, NULL);
pthread_mutex_lock(&buttons.tapDelayMutex);
variant.curr->buttonRelease();
2015-07-26 17:41:12 +00:00
TOUCH_JOY_LOG(">>> [DELAYEDTAP] end ...");
} while (1);
pthread_mutex_unlock(&buttons.tapDelayMutex);
LOG(">>> [DELAYEDTAP] thread exit ...");
return NULL;
}
// ----------------------------------------------------------------------------
static void gltouchjoy_setup(void) {
LOG("gltouchjoy_setup ...");
2015-04-02 02:59:38 +00:00
variant.kpad->resetState();
variant.joys->resetState();
mdlDestroyModel(&axes.model);
mdlDestroyModel(&buttons.model);
2015-04-02 02:59:38 +00:00
joyglobals.isShuttingDown = false;
assert((buttons.tapDelayThreadId == 0) && "setup called multiple times!");
pthread_create(&buttons.tapDelayThreadId, NULL, (void *)&_button_tap_delayed_thread, (void *)NULL);
axes.model = mdlCreateQuad(-1.05, -1.0, AXIS_OBJ_W, AXIS_OBJ_H, MODEL_DEPTH, AXIS_FB_WIDTH, AXIS_FB_HEIGHT, (GLCustom){
.create = &_create_touchjoy_hud,
.setup = &_setup_axis_object,
.destroy = &glhud_destroyDefault,
});
if (!axes.model) {
LOG("gltouchjoy not initializing axis");
2015-04-02 02:59:38 +00:00
return;
}
2015-05-22 05:31:06 +00:00
if (!axes.model->custom) {
LOG("gltouchjoy axes initialization problem");
return;
}
2015-04-02 02:59:38 +00:00
// button object
buttons.model = mdlCreateQuad(1.05-BUTTON_OBJ_W, -1.0, BUTTON_OBJ_W, BUTTON_OBJ_H, MODEL_DEPTH, BUTTON_FB_WIDTH, BUTTON_FB_HEIGHT, (GLCustom){
.create = &_create_touchjoy_hud,
.setup = &_setup_button_object,
.destroy = &glhud_destroyDefault,
});
if (!buttons.model) {
2015-04-02 02:59:38 +00:00
LOG("gltouchjoy not initializing buttons");
return;
}
2015-05-22 05:31:06 +00:00
if (!buttons.model->custom) {
LOG("gltouchjoy buttons initialization problem");
return;
}
2015-04-02 02:59:38 +00:00
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
axes.timingBegin = now;
buttons.timingBegin = now;
joyglobals.isAvailable = true;
2015-04-02 02:59:38 +00:00
}
static void gltouchjoy_shutdown(void) {
LOG("gltouchjoy_shutdown ...");
if (!joyglobals.isAvailable) {
2015-04-02 02:59:38 +00:00
return;
}
variant.kpad->resetState();
variant.joys->resetState();
joyglobals.isAvailable = false;
2015-04-02 02:59:38 +00:00
joyglobals.isShuttingDown = true;
pthread_cond_signal(&buttons.tapDelayCond);
if (pthread_join(buttons.tapDelayThreadId, NULL)) {
ERRLOG("OOPS: pthread_join tap delay thread ...");
}
buttons.tapDelayThreadId = 0;
buttons.tapDelayMutex = (pthread_mutex_t){ 0 };
buttons.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
mdlDestroyModel(&axes.model);
mdlDestroyModel(&buttons.model);
2015-04-02 02:59:38 +00:00
}
static void gltouchjoy_render(void) {
if (!joyglobals.isAvailable) {
2015-04-02 02:59:38 +00:00
return;
}
if (!joyglobals.isEnabled) {
2015-04-02 02:59:38 +00:00
return;
}
if (!joyglobals.ownsScreen) {
return;
}
2015-04-02 02:59:38 +00:00
glViewport(0, 0, touchport.width, touchport.height); // NOTE : show these HUD elements beyond the A2 framebuffer dimensions
// draw axis
float alpha = _get_component_visibility(axes.timingBegin);
if (alpha > 0.0) {
glUniform1f(alphaValue, alpha);
glActiveTexture(TEXTURE_ACTIVE_TOUCHJOY_AXIS);
glBindTexture(GL_TEXTURE_2D, axes.model->textureName);
if (axes.model->texDirty) {
axes.model->texDirty = false;
glTexImage2D(GL_TEXTURE_2D, /*level*/0, TEX_FORMAT_INTERNAL, axes.model->texWidth, axes.model->texHeight, /*border*/0, TEX_FORMAT, TEX_TYPE, axes.model->texPixels);
}
if (axes.modelDirty) {
axes.modelDirty = false;
glBindBuffer(GL_ARRAY_BUFFER, axes.model->posBufferName);
glBufferData(GL_ARRAY_BUFFER, axes.model->positionArraySize, axes.model->positions, GL_DYNAMIC_DRAW);
}
glUniform1i(uniformTex2Use, TEXTURE_ID_TOUCHJOY_AXIS);
glhud_renderDefault(axes.model);
2015-04-02 02:59:38 +00:00
}
// draw button(s)
alpha = _get_component_visibility(buttons.timingBegin);
if (alpha > 0.0) {
glUniform1f(alphaValue, alpha);
glActiveTexture(TEXTURE_ACTIVE_TOUCHJOY_BUTTON);
glBindTexture(GL_TEXTURE_2D, buttons.model->textureName);
if (buttons.model->texDirty) {
buttons.model->texDirty = false;
glTexImage2D(GL_TEXTURE_2D, /*level*/0, TEX_FORMAT_INTERNAL, buttons.model->texWidth, buttons.model->texHeight, /*border*/0, TEX_FORMAT, TEX_TYPE, buttons.model->texPixels);
}
if (buttons.modelDirty) {
buttons.modelDirty = false;
glBindBuffer(GL_ARRAY_BUFFER, buttons.model->posBufferName);
glBufferData(GL_ARRAY_BUFFER, buttons.model->positionArraySize, buttons.model->positions, GL_DYNAMIC_DRAW);
}
glUniform1i(uniformTex2Use, TEXTURE_ID_TOUCHJOY_BUTTON);
glhud_renderDefault(buttons.model);
2015-04-02 02:59:38 +00:00
}
}
static void gltouchjoy_reshape(int w, int h) {
LOG("gltouchjoy_reshape(%d, %d)", w, h);
if (joyglobals.axisIsOnLeft) {
touchport.axisX = 0;
touchport.axisY = 0;
touchport.buttonY = 0;
if (w >= touchport.width) {
touchport.width = w;
touchport.axisXMax = (w * joyglobals.screenDivider);
touchport.buttonX = (w * joyglobals.screenDivider);
touchport.buttonXMax = w;
}
if (h >= touchport.height) {
touchport.height = h;
touchport.axisYMax = h;
touchport.buttonYMax = h;
}
} else {
touchport.buttonX = 0;
touchport.buttonY = 0;
touchport.axisY = 0;
if (w >= touchport.width) {
touchport.width = w;
touchport.buttonXMax = (w * joyglobals.screenDivider);
touchport.axisX = (w * joyglobals.screenDivider);
touchport.axisXMax = w;
}
if (h >= touchport.height) {
touchport.height = h;
touchport.buttonYMax = h;
touchport.axisYMax = h;
}
2015-04-02 02:59:38 +00:00
}
}
static void gltouchjoy_resetJoystick(void) {
// no-op
}
static inline bool _is_point_on_axis_side(int x, int y) {
return (x >= touchport.axisX && x <= touchport.axisXMax && y >= touchport.axisY && y <= touchport.axisYMax);
2015-04-02 02:59:38 +00:00
}
static inline bool _is_point_on_button_side(int x, int y) {
return (x >= touchport.buttonX && x <= touchport.buttonXMax && y >= touchport.buttonY && y <= touchport.buttonYMax);
2015-04-02 02:59:38 +00:00
}
2015-04-10 05:49:53 +00:00
static inline void _reset_model_position(GLModel *model, float touchX, float touchY, float objHalfW, float objHalfH) {
2015-04-02 02:59:38 +00:00
float centerX = 0.f;
float centerY = 0.f;
2015-04-19 21:28:47 +00:00
glhud_screenToModel(touchX, touchY, touchport.width, touchport.height, &centerX, &centerY);
2015-04-02 02:59:38 +00:00
/* 2...3
* .
* .
* .
* 0...1
*/
GLfloat *quad = (GLfloat *)(model->positions);
quad[0 +0] = centerX-objHalfW;
quad[0 +1] = centerY-objHalfH;
quad[4 +0] = centerX+objHalfW;
quad[4 +1] = centerY-objHalfH;
quad[8 +0] = centerX-objHalfW;
quad[8 +1] = centerY+objHalfH;
quad[12+0] = centerX+objHalfW;
quad[12+1] = centerY+objHalfH;
}
2015-04-02 02:59:38 +00:00
static inline void _axis_touch_down(int x, int y) {
axes.centerX = x;
axes.centerY = y;
2015-04-02 02:59:38 +00:00
_reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H);
axes.modelDirty = true;
TOUCH_JOY_LOG("---TOUCH %sDOWN (axis index %d) center:(%d,%d) -> joy(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), axes.trackingIndex, axes.centerX, axes.centerY, joy_x, joy_y);
variant.curr->axisDown();
}
static inline void _button_touch_down(int x, int y) {
variant.curr->setCurrButtonValue(buttons.touchDownChar, buttons.touchDownScancode);
_signal_tap_delay();
2015-04-02 02:59:38 +00:00
buttons.centerX = x;
buttons.centerY = y;
_reset_model_position(buttons.model, x, y, BUTTON_OBJ_HALF_W, BUTTON_OBJ_HALF_H);
buttons.modelDirty = true;
TOUCH_JOY_LOG("---TOUCH %sDOWN (buttons index %d) center:(%d,%d) -> buttons(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), buttons.trackingIndex, buttons.centerX, buttons.centerY, joy_button0, joy_button1);
}
2015-04-02 02:59:38 +00:00
static inline void _axis_move(int x, int y) {
x = (x - axes.centerX);
y = (y - axes.centerY);
TOUCH_JOY_LOG("---TOUCH MOVE ...tracking axis:%d (%d,%d) -> joy(0x%02X,0x%02X)", axes.trackingIndex, x, y, joy_x, joy_y);
variant.curr->axisMove(x, y);
}
static inline void _button_move(int x, int y) {
x -= buttons.centerX;
y -= buttons.centerY;
if ((y < -joyglobals.switchThreshold) || (y > joyglobals.switchThreshold)) {
touchjoy_button_type_t theButtonChar = -1;
int theButtonScancode = -1;
if (y < 0) {
theButtonChar = buttons.northChar;
theButtonScancode = buttons.northScancode;
} else {
theButtonChar = buttons.southChar;
theButtonScancode = buttons.southScancode;
}
variant.curr->setCurrButtonValue(theButtonChar, theButtonScancode);
_signal_tap_delay();
TOUCH_JOY_LOG("+++TOUCH MOVE ...tracking button:%d (%d,%d) -> buttons(0x%02X,0x%02X)", buttons.trackingIndex, x, y, joy_button0, joy_button1);
}
}
static inline void _axis_touch_up(int x, int y) {
x = (x - axes.centerX);
y = (y - axes.centerY);
if (buttons.trackingIndex > axes.trackingIndex) {
--buttons.trackingIndex;
}
variant.curr->axisUp(x, y);
#if DEBUG_TOUCH_JOY
bool resetIndex = false;
if (buttons.trackingIndex > axes.trackingIndex) {
// TODO FIXME : is resetting the pointer index just an Android-ism?
resetIndex = true;
}
TOUCH_JOY_LOG("---TOUCH %sUP (axis went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset buttons index!)" : ""));
#endif
axes.trackingIndex = TOUCH_NONE;
}
static inline void _button_touch_up(void) {
#if DEBUG_TOUCH_JOY
bool resetIndex = false;
if (axes.trackingIndex > buttons.trackingIndex) {
// TODO FIXME : is resetting the pointer index just an Android-ism?
resetIndex = true;
}
TOUCH_JOY_LOG("---TOUCH %sUP (buttons went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset axis index!)" : ""));
#endif
if (axes.trackingIndex > buttons.trackingIndex) {
--axes.trackingIndex;
}
buttons.trackingIndex = TOUCH_NONE;
_signal_tap_delay();
2015-04-02 02:59:38 +00:00
}
static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) {
2015-04-02 02:59:38 +00:00
if (!joyglobals.isAvailable) {
return 0x0LL;
2015-04-02 02:59:38 +00:00
}
if (!joyglobals.isEnabled) {
return 0x0LL;
2015-04-02 02:59:38 +00:00
}
if (!joyglobals.ownsScreen) {
return 0x0LL;
}
2015-04-02 02:59:38 +00:00
bool axisConsumed = false;
bool buttonConsumed = false;
switch (action) {
case TOUCH_DOWN:
case TOUCH_POINTER_DOWN:
{
int x = (int)x_coords[pointer_idx];
int y = (int)y_coords[pointer_idx];
if (_is_point_on_axis_side(x, y)) {
axisConsumed = true;
axes.trackingIndex = pointer_idx;
_axis_touch_down(x, y);
} else if (_is_point_on_button_side(x, y)) {
buttonConsumed = true;
buttons.trackingIndex = pointer_idx;
_button_touch_down(x, y);
} else {
assert(false && "should either be on axis or button side");
}
2015-04-02 02:59:38 +00:00
}
break;
case TOUCH_MOVE:
if (axes.trackingIndex >= 0) {
2015-04-02 02:59:38 +00:00
axisConsumed = true;
int x = (int)x_coords[axes.trackingIndex];
int y = (int)y_coords[axes.trackingIndex];
_axis_move(x, y);
}
if (buttons.trackingIndex >= 0) {
buttonConsumed = true;
int x = (int)x_coords[buttons.trackingIndex];
int y = (int)y_coords[buttons.trackingIndex];
_button_move(x, y);
2015-04-02 02:59:38 +00:00
}
break;
case TOUCH_UP:
case TOUCH_POINTER_UP:
if (pointer_idx == axes.trackingIndex) {
int x = (int)x_coords[axes.trackingIndex];
int y = (int)y_coords[axes.trackingIndex];
_axis_touch_up(x, y);
} else if (pointer_idx == buttons.trackingIndex) {
_button_touch_up();
} else {
if (pointer_count == 1) {
// last pointer up completely resets state
LOG("!!!! ... RESETTING TOUCH JOYSTICK STATE MACHINE");
variant.joys->resetState();
variant.kpad->resetState();
}
2015-04-02 02:59:38 +00:00
}
break;
case TOUCH_CANCEL:
LOG("---TOUCH CANCEL");
variant.joys->resetState();
variant.kpad->resetState();
2015-04-02 02:59:38 +00:00
break;
default:
LOG("!!! UNKNOWN TOUCH EVENT : %d", action);
2015-04-02 02:59:38 +00:00
break;
}
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (axisConsumed) {
axes.timingBegin = now;
2015-04-02 02:59:38 +00:00
}
if (buttonConsumed) {
buttons.timingBegin = now;
}
return TOUCH_FLAGS_HANDLED | TOUCH_FLAGS_JOY;
2015-04-02 02:59:38 +00:00
}
static bool gltouchjoy_isTouchJoystickAvailable(void) {
return joyglobals.isAvailable;
2015-04-02 02:59:38 +00:00
}
static void gltouchjoy_setTouchJoystickEnabled(bool enabled) {
joyglobals.isEnabled = enabled;
2015-04-02 02:59:38 +00:00
}
static void gltouchjoy_setTouchJoystickOwnsScreen(bool pwnd) {
joyglobals.ownsScreen = pwnd;
if (joyglobals.ownsScreen) {
joyglobals.minAlpha = joyglobals.minAlphaWhenOwnsScreen;
} else {
joyglobals.minAlpha = 0.0;
}
}
static bool gltouchjoy_ownsScreen(void) {
return joyglobals.ownsScreen;
}
static void _animation_showTouchJoystick(void) {
if (!joyglobals.isAvailable) {
return;
}
int x = touchport.axisX + ((touchport.axisXMax - touchport.axisX)/2);
int y = touchport.axisY + ((touchport.axisYMax - touchport.axisY)/2);
_reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H);
axes.modelDirty = true;
x = touchport.buttonX + ((touchport.buttonXMax - touchport.buttonX)/2);
y = touchport.buttonY + ((touchport.buttonYMax - touchport.buttonY)/2);
_reset_model_position(buttons.model, x, y, BUTTON_OBJ_HALF_W, BUTTON_OBJ_HALF_H);
buttons.modelDirty = true;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
axes.timingBegin = now;
buttons.timingBegin = now;
}
static void _animation_hideTouchJoystick(void) {
if (!joyglobals.isAvailable) {
return;
}
axes.timingBegin = (struct timespec){ 0 };
buttons.timingBegin = (struct timespec){ 0 };
}
static void gltouchjoy_setTouchButtonTypes(
touchjoy_button_type_t touchDownChar, int touchDownScancode,
touchjoy_button_type_t northChar, int northScancode,
touchjoy_button_type_t southChar, int southScancode)
{
buttons.touchDownChar = touchDownChar;
buttons.touchDownScancode = touchDownScancode;
buttons.southChar = southChar;
buttons.southScancode = southScancode;
buttons.northChar = northChar;
buttons.northScancode = northScancode;
buttons.activeChar = ICONTEXT_NONACTIONABLE;
char currButtonDisplayChar = touchDownChar;
if (touchDownChar == TOUCH_BUTTON0) {
currButtonDisplayChar = MOUSETEXT_OPENAPPLE;
} else if (touchDownChar == TOUCH_BUTTON1) {
currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE;
} else if (touchDownChar == TOUCH_BOTH) {
currButtonDisplayChar = '+';
}
_setup_button_object_with_char(currButtonDisplayChar);
2015-04-02 02:59:38 +00:00
}
static void gltouchjoy_setTapDelay(float secs) {
if (UNLIKELY(secs < 0.f)) {
ERRLOG("Clamping tap delay to 0.0 secs");
}
if (UNLIKELY(secs > 1.f)) {
ERRLOG("Clamping tap delay to 1.0 secs");
}
buttons.tapDelayNanos = (unsigned int)((float)NANOSECONDS_PER_SECOND * secs);
}
static void gltouchjoy_setTouchAxisSensitivity(float multiplier) {
axes.multiplier = multiplier;
}
static void gltouchjoy_setButtonSwitchThreshold(int delta) {
joyglobals.switchThreshold = delta;
}
static void gltouchjoy_setTouchVariant(touchjoy_variant_t variantType) {
variant.curr->resetState();
switch (variantType) {
case EMULATED_JOYSTICK:
variant.curr = variant.joys;
break;
case EMULATED_KEYPAD:
variant.curr = variant.kpad;
break;
default:
assert(false && "touch variant set to invalid");
break;
}
variant.curr->resetState();
2015-04-02 02:59:38 +00:00
}
static touchjoy_variant_t gltouchjoy_getTouchVariant(void) {
return variant.curr->variant();
}
static void gltouchjoy_setTouchAxisTypes(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]) {
memcpy(axes.rosetteChars, rosetteChars, sizeof(uint8_t)*(ROSETTE_ROWS * ROSETTE_COLS));
memcpy(axes.rosetteScancodes, rosetteScancodes, sizeof(int) *(ROSETTE_ROWS * ROSETTE_COLS));
_setup_axis_object(axes.model);
2015-04-02 02:59:38 +00:00
}
static void gltouchjoy_setScreenDivision(float screenDivider) {
joyglobals.screenDivider = screenDivider;
// force reshape here to apply changes ...
gltouchjoy_reshape(touchport.width, touchport.height);
}
static void gltouchjoy_setAxisOnLeft(bool axisIsOnLeft) {
joyglobals.axisIsOnLeft = axisIsOnLeft;
// force reshape here to apply changes ...
gltouchjoy_reshape(touchport.width, touchport.height);
}
static void gltouchjoy_beginCalibration(void) {
video_clear();
joyglobals.isCalibrating = true;
}
static void gltouchjoy_endCalibration(void) {
video_redraw();
joyglobals.isCalibrating = false;
}
static bool gltouchjoy_isCalibrating(void) {
return joyglobals.isCalibrating;
}
2015-04-18 05:12:13 +00:00
__attribute__((constructor(CTOR_PRIORITY_LATE)))
2015-04-02 02:59:38 +00:00
static void _init_gltouchjoy(void) {
LOG("Registering OpenGL software touch joystick");
axes.centerX = 240;
axes.centerY = 160;
axes.multiplier = 1.f;
axes.trackingIndex = TOUCH_NONE;
axes.rosetteChars[0] = ' ';
axes.rosetteScancodes[0] = -1;
axes.rosetteChars[1] = MOUSETEXT_UP;
axes.rosetteScancodes[1] = -1;
axes.rosetteChars[2] = ' ';
axes.rosetteScancodes[2] = -1;
axes.rosetteChars[3] = MOUSETEXT_LEFT;
axes.rosetteScancodes[3] = -1;
axes.rosetteChars[4] = '+';
axes.rosetteScancodes[4] = -1;
axes.rosetteChars[5] = MOUSETEXT_RIGHT;
axes.rosetteScancodes[5] = -1;
axes.rosetteChars[6] = ' ';
axes.rosetteScancodes[6] = -1;
axes.rosetteChars[7] = MOUSETEXT_DOWN;
axes.rosetteScancodes[7] = -1;
axes.rosetteChars[8] = ' ';
axes.rosetteScancodes[8] = -1;
buttons.centerX = 240;
buttons.centerY = 160;
buttons.trackingIndex = TOUCH_NONE;
buttons.touchDownChar = TOUCH_BUTTON0;
buttons.touchDownScancode = -1;
buttons.southChar = TOUCH_BUTTON1;
buttons.southScancode = -1;
buttons.northChar = TOUCH_BOTH;
buttons.northScancode = -1;
buttons.activeChar = MOUSETEXT_OPENAPPLE;
buttons.tapDelayThreadId = 0;
buttons.tapDelayMutex = (pthread_mutex_t){ 0 };
buttons.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
buttons.tapDelayNanos = BUTTON_TAP_DELAY_NANOS_DEFAULT;
joyglobals.isEnabled = true;
joyglobals.ownsScreen = true;
joyglobals.screenDivider = 0.5f;
joyglobals.axisIsOnLeft = true;
joyglobals.switchThreshold = BUTTON_SWITCH_THRESHOLD_DEFAULT;
video_backend->animation_showTouchJoystick = &_animation_showTouchJoystick;
video_backend->animation_hideTouchJoystick = &_animation_hideTouchJoystick;
2015-04-02 02:59:38 +00:00
joydriver_isTouchJoystickAvailable = &gltouchjoy_isTouchJoystickAvailable;
joydriver_setTouchJoystickEnabled = &gltouchjoy_setTouchJoystickEnabled;
joydriver_setTouchJoystickOwnsScreen = &gltouchjoy_setTouchJoystickOwnsScreen;
joydriver_ownsScreen = &gltouchjoy_ownsScreen;
joydriver_setTouchButtonTypes = &gltouchjoy_setTouchButtonTypes;
joydriver_setTapDelay = &gltouchjoy_setTapDelay;
joydriver_setTouchAxisSensitivity = &gltouchjoy_setTouchAxisSensitivity;
joydriver_setButtonSwitchThreshold = &gltouchjoy_setButtonSwitchThreshold;
joydriver_setTouchVariant = &gltouchjoy_setTouchVariant;
joydriver_getTouchVariant = &gltouchjoy_getTouchVariant;
joydriver_setTouchAxisTypes = &gltouchjoy_setTouchAxisTypes;
joydriver_setScreenDivision = &gltouchjoy_setScreenDivision;
joydriver_setAxisOnLeft = &gltouchjoy_setAxisOnLeft;
joydriver_beginCalibration = &gltouchjoy_beginCalibration;
joydriver_endCalibration = &gltouchjoy_endCalibration;
joydriver_isCalibrating = &gltouchjoy_isCalibrating;
2015-04-02 02:59:38 +00:00
glnode_registerNode(RENDER_LOW, (GLNode){
2015-04-12 23:20:01 +00:00
.setup = &gltouchjoy_setup,
.shutdown = &gltouchjoy_shutdown,
.render = &gltouchjoy_render,
.reshape = &gltouchjoy_reshape,
.onTouchEvent = &gltouchjoy_onTouchEvent,
});
2015-04-02 02:59:38 +00:00
}
void gltouchjoy_registerVariant(touchjoy_variant_t variantType, GLTouchJoyVariant *touchJoyVariant) {
switch (variantType) {
case EMULATED_JOYSTICK:
variant.joys = touchJoyVariant;
variant.curr = variant.kpad;
break;
case EMULATED_KEYPAD:
variant.kpad = touchJoyVariant;
variant.curr = variant.kpad;
break;
default:
assert(false && "invalid touch variant registration");
break;
}
}
2015-04-02 02:59:38 +00:00
void gldriver_joystick_reset(void) {
#warning FIXME TODO expunge this olde API ...
2015-04-02 02:59:38 +00:00
}