Refactor GL Touch Joystick (PART I Native changes)

- Move touch-handling code for "regular" touch joystick into separate file
    - Add new touch-handling code for "keypad" touch joystick.  This is a somewhat complex state-machine to determine
      which key to press depending on a full keypad rosette.
    - Adds a callback to vm.c to hook into keyboard read callback.  Allows us to immediately queue the next key if the
      keypad touch joystick is long-pressed for either axis or button keys.
This commit is contained in:
Aaron Culliney 2015-08-15 22:02:51 -07:00
parent 77e96ba1ec
commit 5c3791f5dc
11 changed files with 999 additions and 243 deletions

View File

@ -109,7 +109,7 @@ jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCurrentTouchDevice(JN
touchjoy_variant_t variant = joydriver_getTouchVariant();
if (variant == EMULATED_JOYSTICK) {
return ANDROID_TOUCH_JOYSTICK;
} else if (variant == EMULATED_JOYSTICK) {
} else if (variant == EMULATED_KEYPAD) {
return ANDROID_TOUCH_JOYSTICK_KEYPAD;
}
} else if (keydriver_ownsScreen()) {
@ -147,7 +147,19 @@ void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonTy
return;
}
joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, (touchjoy_button_type_t)northButton, (touchjoy_button_type_t)southButton);
uint8_t rosetteChars[ROSETTE_COLS * ROSETTE_ROWS];
int rosetteScancodes[ROSETTE_COLS * ROSETTE_ROWS];
rosetteChars[ROSETTE_NORTHWEST] = ' '; rosetteScancodes[ROSETTE_NORTHWEST] = -1;
rosetteChars[ROSETTE_NORTH] = (uint8_t)MOUSETEXT_UP; rosetteScancodes[ROSETTE_NORTH] = -1;
rosetteChars[ROSETTE_NORTHEAST] = ' '; rosetteScancodes[ROSETTE_NORTHEAST] = -1;
rosetteChars[ROSETTE_WEST] = (uint8_t)MOUSETEXT_LEFT; rosetteScancodes[ROSETTE_WEST] = -1;
rosetteChars[ROSETTE_CENTER] = '+'; rosetteScancodes[ROSETTE_CENTER] = -1;
rosetteChars[ROSETTE_EAST] = (uint8_t)MOUSETEXT_RIGHT; rosetteScancodes[ROSETTE_EAST] = -1;
rosetteChars[ROSETTE_SOUTHWEST] = ' '; rosetteScancodes[ROSETTE_SOUTHWEST] = -1;
rosetteChars[ROSETTE_SOUTH] = (uint8_t)MOUSETEXT_DOWN; rosetteScancodes[ROSETTE_SOUTH] = -1;
rosetteChars[ROSETTE_SOUTHEAST] = ' '; rosetteScancodes[ROSETTE_SOUTHEAST] = -1;
joydriver_setTouchAxisTypes(rosetteChars, rosetteScancodes);
joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, -1, (touchjoy_button_type_t)northButton, -1, (touchjoy_button_type_t)southButton, -1);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickTapDelay(JNIEnv *env, jclass cls, jfloat secs) {
@ -175,6 +187,41 @@ void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetAxisOnLe
joydriver_setAxisOnLeft(axisIsOnLeft);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetKeypadTypes(JNIEnv *env, jclass cls,
jintArray jRosetteChars, jintArray jRosetteScans, jintArray jButtonsChars, jintArray jButtonsScans)
{
jint *rosetteChars = (*env)->GetIntArrayElements(env, jRosetteChars, 0);
jint *rosetteScans = (*env)->GetIntArrayElements(env, jRosetteScans, 0);
jint *buttonsChars = (*env)->GetIntArrayElements(env, jButtonsChars, 0);
jint *buttonsScans = (*env)->GetIntArrayElements(env, jButtonsScans, 0);
LOG("nativeTouchJoystickSetKeypadValues() : NW:%c/%d, N:%c/%d, NE:%c/%d, ... SWIPEUP:%c/%d",
(char)rosetteChars[0], rosetteScans[0], (char)rosetteChars[1], rosetteScans[1], (char)rosetteChars[2], rosetteScans[2],
(char)buttonsChars[1], buttonsScans[1]);
LOG("nativeTouchJoystickSetKeypadValues() : W:%c/%d, C:%c/%d, E:%c/%d, ... TAP:%c/%d",
(char)rosetteChars[3], rosetteScans[3], (char)rosetteChars[4], rosetteScans[4], (char)rosetteChars[5], rosetteScans[5],
(char)buttonsChars[0], buttonsScans[0]);
LOG("nativeTouchJoystickSetKeypadValues() : SW:%c/%d, S:%c/%d, SE:%c/%d, ... SWIPEDN:%c/%d",
(char)rosetteChars[6], rosetteScans[6], (char)rosetteChars[7], rosetteScans[7], (char)rosetteChars[8], rosetteScans[8],
(char)buttonsChars[2], buttonsScans[2]);
// we could just pass these as jcharArray ... but this isn't a tight loop =P
uint8_t actualChars[ROSETTE_ROWS * ROSETTE_COLS];
for (unsigned int i=0; i<(ROSETTE_ROWS * ROSETTE_COLS); i++) {
actualChars[i] = (uint8_t)rosetteChars[i];
}
joydriver_setTouchAxisTypes(actualChars, rosetteScans);
joydriver_setTouchButtonTypes(
(touchjoy_button_type_t)buttonsChars[0], buttonsScans[0],
(touchjoy_button_type_t)buttonsChars[1], buttonsScans[1],
(touchjoy_button_type_t)buttonsChars[2], buttonsScans[2]);
(*env)->ReleaseIntArrayElements(env, jRosetteChars, rosetteChars, 0);
(*env)->ReleaseIntArrayElements(env, jRosetteScans, rosetteScans, 0);
(*env)->ReleaseIntArrayElements(env, jButtonsChars, buttonsChars, 0);
(*env)->ReleaseIntArrayElements(env, jButtonsScans, buttonsScans, 0);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickBeginCalibrationMode(JNIEnv *env, jclass cls) {
LOG("nativeTouchJoystickBeginCalibrationMode() ...");
joydriver_beginCalibration();

View File

@ -15,6 +15,8 @@ APPLE2_VIDEO_SRC = \
$(APPLE2_SRC_PATH)/video/glhudmodel.c \
$(APPLE2_SRC_PATH)/video/glalert.c \
$(APPLE2_SRC_PATH)/video/gltouchjoy.c \
$(APPLE2_SRC_PATH)/video/gltouchjoy_joy.c \
$(APPLE2_SRC_PATH)/video/gltouchjoy_kpad.c \
$(APPLE2_SRC_PATH)/video/gltouchkbd.c \
$(APPLE2_SRC_PATH)/video/gltouchmenu.c \
$(APPLE2_SRC_PATH)/video_util/matrixUtil.c \

View File

@ -261,17 +261,20 @@ bool (*joydriver_isTouchJoystickAvailable)(void) = NULL;
void (*joydriver_setTouchJoystickEnabled)(bool enabled) = NULL;
void (*joydriver_setTouchJoystickOwnsScreen)(bool pwnd) = NULL;
bool (*joydriver_ownsScreen)(void) = NULL;
void (*joydriver_setTouchButtonValues)(char button0Val, char button1Val, char buttonBothVal) = NULL;
void (*joydriver_setTouchButtonTypes)(touchjoy_button_type_t down, touchjoy_button_type_t north, touchjoy_button_type_t south) = NULL;
void (*joydriver_setTouchButtonTypes)(
touchjoy_button_type_t touchDownChar, int downScancode,
touchjoy_button_type_t northChar, int northScancode,
touchjoy_button_type_t southChar, int southScancode) = NULL;
void (*joydriver_setTapDelay)(float secs) = NULL;
void (*joydriver_setTouchAxisSensitivity)(float multiplier) = NULL;
void (*joydriver_setButtonSwitchThreshold)(int delta) = NULL;
void (*joydriver_setTouchVariant)(touchjoy_variant_t variant) = NULL;
touchjoy_variant_t (*joydriver_getTouchVariant)(void) = NULL;
void (*joydriver_setTouchAxisValues)(char up, char left, char right, char down) = NULL;
void (*joydriver_setTouchAxisTypes)(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]) = NULL;
void (*joydriver_setScreenDivision)(float division) = NULL;
void (*joydriver_setAxisOnLeft)(bool axisIsOnLeft) = NULL;
void (*joydriver_beginCalibration)(void) = NULL;
void (*joydriver_endCalibration)(void) = NULL;
bool (*joydriver_isCalibrating)(void) = NULL;
#endif

View File

@ -44,8 +44,24 @@ typedef enum touchjoy_button_type_t {
TOUCH_BUTTON0 = 0,
TOUCH_BUTTON1,
TOUCH_BOTH,
// --or-- an ASCII/fonttext value ...
} touchjoy_button_type_t;
#define ROSETTE_ROWS 3
#define ROSETTE_COLS 3
enum {
ROSETTE_NORTHWEST=0,
ROSETTE_NORTH,
ROSETTE_NORTHEAST,
ROSETTE_WEST,
ROSETTE_CENTER,
ROSETTE_EAST,
ROSETTE_SOUTHWEST,
ROSETTE_SOUTH,
ROSETTE_SOUTHEAST,
};
// is the touch joystick available
extern bool (*joydriver_isTouchJoystickAvailable)(void);
@ -58,16 +74,24 @@ extern void (*joydriver_setTouchJoystickOwnsScreen)(bool pwnd);
// query touch screen ownership
extern bool (*joydriver_ownsScreen)(void);
// set the joystick button visuals (these values are also fired for keyboard variant)
extern void (*joydriver_setTouchButtonValues)(char button0Val, char button1Val, char buttonBothVal);
/*
* set the joystick button types/visuals (scancodes are fired for EMULATED_KEYPAD variant)
*
* - for EMULATED_JOYSTICK, there is an implicit extra layer-of-indirection for the touchjoy_button_type_t, which maps
* to the open apple, closed apple, or "both" visual keys
*
* - for EMULATED_KEYPAD, the touchjoy_button_type_t is the displayed visual (as ASCII value and lookup into font
* table)
*/
extern void (*joydriver_setTouchButtonTypes)(
touchjoy_button_type_t touchDownChar, int downScancode,
touchjoy_button_type_t northChar, int northScancode,
touchjoy_button_type_t southChar, int southScancode);
// set the joystick button types (for joystick variant)
extern void (*joydriver_setTouchButtonTypes)(touchjoy_button_type_t down, touchjoy_button_type_t north, touchjoy_button_type_t south);
// set the tap delay (to differentiate between single tap and north/south/etc swipe)
// set the button tap delay (to differentiate between single tap and north/south/etc swipe)
extern void (*joydriver_setTapDelay)(float secs);
// set the touch axis sensitivity multiplier
// set the sensitivity multiplier
extern void (*joydriver_setTouchAxisSensitivity)(float multiplier);
// set the touch button switch threshold
@ -79,8 +103,8 @@ extern void (*joydriver_setTouchVariant)(touchjoy_variant_t variant);
// get the joystick variant
extern touchjoy_variant_t (*joydriver_getTouchVariant)(void);
// set the axis visuals (these key values are also fired for keyboard variant)
extern void (*joydriver_setTouchAxisValues)(char north, char west, char east, char south);
// set the axis visuals (scancodes are fired for EMULATED_KEYPAD variant)
extern void (*joydriver_setTouchAxisTypes)(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]);
// set screen divide between axis and buttons
extern void (*joydriver_setScreenDivision)(float division);
@ -94,6 +118,9 @@ extern void (*joydriver_beginCalibration)(void);
// end calibration mode
extern void (*joydriver_endCalibration)(void);
// end calibration mode
extern bool (*joydriver_isCalibrating)(void);
#endif // INTERFACE_TOUCH
#endif // whole file

View File

@ -494,5 +494,6 @@ bool (*keydriver_isTouchKeyboardAvailable)(void) = NULL;
void (*keydriver_setTouchKeyboardEnabled)(bool enabled) = NULL;
void (*keydriver_setTouchKeyboardOwnsScreen)(bool pwnd) = NULL;
bool (*keydriver_ownsScreen)(void) = NULL;
void (*keydriver_keyboardReadCallback)(void) = NULL;
#endif

View File

@ -160,6 +160,9 @@ extern void (*keydriver_setTouchKeyboardOwnsScreen)(bool pwnd);
// query touch screen ownership
extern bool (*keydriver_ownsScreen)(void);
// keyboard read callback
extern void (*keydriver_keyboardReadCallback)(void);
#endif
#endif

View File

@ -9,10 +9,7 @@
*
*/
#include "common.h"
#include "video/glvideo.h"
#include "video/glhudmodel.h"
#include "video/glnode.h"
#include "video/gltouchjoy.h"
#if !INTERFACE_TOUCH
#error this is a touch interface module, possibly you mean to not compile this at all?
@ -52,18 +49,15 @@
#define BUTTON_SWITCH_THRESHOLD_DEFAULT 22
#define BUTTON_TAP_DELAY_NANOS_DEFAULT 50000000
// module globals
GLTouchJoyGlobals joyglobals = { 0 };
GLTouchJoyAxes axes = { 0 };
GLTouchJoyButtons buttons = { 0 };
static struct {
bool isAvailable; // Were there any OpenGL/memory errors on gltouchjoy initialization?
bool isShuttingDown;
bool isCalibrating; // Are we running in calibration mode?
bool isEnabled; // Does player want touchjoy enabled?
bool ownsScreen; // Does the touchjoy currently own the screen?
float minAlphaWhenOwnsScreen;
float minAlpha;
float screenDivider;
bool axisIsOnLeft;
} joyglobals = { 0 };
GLTouchJoyVariant *joys;
GLTouchJoyVariant *kpad;
GLTouchJoyVariant *curr;
} variant = { 0 };
// viewport touch
static struct {
@ -85,60 +79,6 @@ static struct {
// TODO FIXME : support 2-players!
} touchport = { 0 };
// touch axis variables
static struct {
GLModel *model;
uint8_t northChar;
uint8_t westChar;
uint8_t eastChar;
uint8_t southChar;
bool modelDirty;
touchjoy_variant_t type;
int centerX;
int centerY;
float multiplier;
int trackingIndex;
struct timespec timingBegin;
} axes = { 0 };
// button object variables
static struct {
GLModel *model;
uint8_t activeChar;
bool modelDirty;
touchjoy_button_type_t touchDownButton;
touchjoy_button_type_t northButton;
touchjoy_button_type_t southButton;
uint8_t char0;
uint8_t char1;
uint8_t charBoth;
int switchThreshold;
int centerX;
int centerY;
int trackingIndex;
struct timespec timingBegin;
pthread_t tapDelayThreadId;
pthread_mutex_t tapDelayMutex;
pthread_cond_t tapDelayCond;
unsigned int tapDelayNanos;
volatile uint8_t currButtonValue0;
volatile uint8_t currButtonValue1;
volatile uint8_t currButtonChar;
} buttons = { 0 };
// ----------------------------------------------------------------------------
#warning FIXME TODO ... this can become a common helper function ...
@ -192,10 +132,12 @@ static void _setup_axis_object(GLModel *parent) {
}
const unsigned int row = (AXIS_TEMPLATE_COLS+1);
((hudElement->tpl)+(row*0))[2] = axes.northChar;
((hudElement->tpl)+(row*2))[0] = axes.westChar;
((hudElement->tpl)+(row*2))[4] = axes.eastChar;
((hudElement->tpl)+(row*4))[2] = axes.southChar;
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);
}
@ -276,17 +218,22 @@ static void *_button_tap_delayed_thread(void *dummyptr) {
nanosleep(&ts, NULL);
pthread_mutex_lock(&buttons.tapDelayMutex);
// set the emulator's joystick button values until touch up
// wait until touch up/cancel
do {
joy_button0 = buttons.currButtonValue0;
joy_button1 = buttons.currButtonValue1;
_setup_button_object_with_char(buttons.currButtonChar);
// 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;
}
TOUCH_JOY_LOG(">>> [DELAYEDTAP] looping ...");
} while (1);
@ -299,8 +246,8 @@ static void *_button_tap_delayed_thread(void *dummyptr) {
nanosleep(&ts, NULL);
pthread_mutex_lock(&buttons.tapDelayMutex);
joy_button0 = 0x0;
joy_button1 = 0x0;
variant.curr->buttonRelease();
TOUCH_JOY_LOG(">>> [DELAYEDTAP] end ...");
} while (1);
@ -316,6 +263,9 @@ static void *_button_tap_delayed_thread(void *dummyptr) {
static void gltouchjoy_setup(void) {
LOG("gltouchjoy_setup ...");
variant.kpad->resetState();
variant.joys->resetState();
mdlDestroyModel(&axes.model);
mdlDestroyModel(&buttons.model);
@ -365,6 +315,9 @@ static void gltouchjoy_shutdown(void) {
return;
}
variant.kpad->resetState();
variant.joys->resetState();
joyglobals.isAvailable = false;
joyglobals.isShuttingDown = true;
@ -387,6 +340,9 @@ static void gltouchjoy_render(void) {
if (!joyglobals.isEnabled) {
return;
}
if (!joyglobals.ownsScreen) {
return;
}
glViewport(0, 0, touchport.width, touchport.height); // NOTE : show these HUD elements beyond the A2 framebuffer dimensions
@ -475,11 +431,11 @@ static void gltouchjoy_resetJoystick(void) {
// no-op
}
static inline bool _is_point_on_axis_side(float x, float y) {
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);
}
static inline bool _is_point_on_button_side(float x, float y) {
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);
}
@ -507,72 +463,91 @@ static inline void _reset_model_position(GLModel *model, float touchX, float tou
quad[12+1] = centerY+objHalfH;
}
static inline void _move_joystick_axis(int x, int y) {
static inline void _axis_touch_down(int x, int y) {
axes.centerX = x;
axes.centerY = y;
x = (x - axes.centerX);
y = (y - axes.centerY);
_reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H);
axes.modelDirty = true;
if (axes.multiplier != 1.f) {
x = (int) ((float)x * axes.multiplier);
y = (int) ((float)y * axes.multiplier);
}
x += 0x80;
y += 0x80;
if (x < 0) {
x = 0;
}
if (x > 0xff) {
x = 0xff;
}
if (y < 0) {
y = 0;
}
if (y > 0xff) {
y = 0xff;
}
joy_x = x;
joy_y = y;
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 _set_current_joy_button_values(int theButton) {
if (theButton == TOUCH_BUTTON0) {
buttons.currButtonValue0 = 0x80;
buttons.currButtonValue1 = 0;
buttons.currButtonChar = buttons.char0;
} else if (theButton == TOUCH_BUTTON1) {
buttons.currButtonValue0 = 0;
buttons.currButtonValue1 = 0x80;
buttons.currButtonChar = buttons.char1;
} else if (theButton == TOUCH_BOTH) {
buttons.currButtonValue0 = 0x80;
buttons.currButtonValue1 = 0x80;
buttons.currButtonChar = buttons.charBoth;
} else {
buttons.currButtonValue0 = 0;
buttons.currButtonValue1 = 0;
static inline void _button_touch_down(int x, int y) {
variant.curr->setCurrButtonValue(buttons.touchDownChar, buttons.touchDownScancode);
_signal_tap_delay();
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);
}
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();
}
static inline void _move_button_axis(int x, int y) {
x -= buttons.centerX;
y -= buttons.centerY;
if ((y < -buttons.switchThreshold) || (y > buttons.switchThreshold)) {
int theButton = -1;
if (y < 0) {
theButton = buttons.northButton;
} else {
theButton = buttons.southButton;
}
_set_current_joy_button_values(theButton);
}
}
static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) {
@ -589,87 +564,63 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point
bool axisConsumed = false;
bool buttonConsumed = false;
float x = x_coords[pointer_idx];
float y = y_coords[pointer_idx];
switch (action) {
case TOUCH_DOWN:
case TOUCH_POINTER_DOWN:
if (_is_point_on_axis_side(x, y)) {
axisConsumed = true;
axes.trackingIndex = pointer_idx;
axes.centerX = (int)x;
axes.centerY = (int)y;
_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);
} else if (_is_point_on_button_side(x, y)) {
buttonConsumed = true;
buttons.trackingIndex = pointer_idx;
_set_current_joy_button_values(buttons.touchDownButton);
buttons.centerX = (int)x;
buttons.centerY = (int)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);
} else {
// ...
{
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");
}
}
break;
case TOUCH_MOVE:
if (axes.trackingIndex >= 0) {
axisConsumed = true;
_move_joystick_axis((int)x_coords[axes.trackingIndex], (int)y_coords[axes.trackingIndex]);
TOUCH_JOY_LOG("---TOUCH MOVE ...tracking axis:%d (%d,%d) -> joy(0x%02X,0x%02X)", axes.trackingIndex,
(int)x_coords[axes.trackingIndex], (int)y_coords[axes.trackingIndex], joy_x, joy_y);
int x = (int)x_coords[axes.trackingIndex];
int y = (int)y_coords[axes.trackingIndex];
_axis_move(x, y);
}
if (buttons.trackingIndex >= 0) {
buttonConsumed = true;
_move_button_axis((int)x_coords[buttons.trackingIndex], (int)y_coords[buttons.trackingIndex]);
TOUCH_JOY_LOG("+++TOUCH MOVE ...tracking button:%d (%d,%d) -> buttons(0x%02X,0x%02X)", buttons.trackingIndex,
(int)x_coords[buttons.trackingIndex], (int)y_coords[buttons.trackingIndex], joy_button0, joy_button1);
int x = (int)x_coords[buttons.trackingIndex];
int y = (int)y_coords[buttons.trackingIndex];
_button_move(x, y);
}
break;
case TOUCH_UP:
case TOUCH_POINTER_UP:
if (pointer_idx == axes.trackingIndex) {
axisConsumed = true;
bool resetIndex = false;
if (buttons.trackingIndex > axes.trackingIndex) {
// TODO FIXME : is resetting the pointer index just an Android-ism?
resetIndex = true;
--buttons.trackingIndex;
}
axes.trackingIndex = TOUCH_NONE;
joy_x = HALF_JOY_RANGE;
joy_y = HALF_JOY_RANGE;
TOUCH_JOY_LOG("---TOUCH %sUP (axis went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset buttons index!)" : ""));
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) {
buttonConsumed = true;
bool resetIndex = false;
if (axes.trackingIndex > buttons.trackingIndex) {
// TODO FIXME : is resetting the pointer index just an Android-ism?
resetIndex = true;
--axes.trackingIndex;
}
buttons.trackingIndex = TOUCH_NONE;
_signal_tap_delay();
TOUCH_JOY_LOG("---TOUCH %sUP (buttons went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset axis index!)" : ""));
_button_touch_up();
} else {
// not tracking tap/gestures originating from control-gesture portion of screen
if (pointer_count == 1) {
// last pointer up completely resets state
LOG("!!!! ... RESETTING TOUCH JOYSTICK STATE MACHINE");
variant.joys->resetState();
variant.kpad->resetState();
}
}
break;
case TOUCH_CANCEL:
LOG("---TOUCH CANCEL");
axes.trackingIndex = TOUCH_NONE;
buttons.trackingIndex = TOUCH_NONE;
joy_x = HALF_JOY_RANGE;
joy_y = HALF_JOY_RANGE;
variant.joys->resetState();
variant.kpad->resetState();
break;
default:
@ -724,16 +675,19 @@ static void _animation_hideTouchJoystick(void) {
buttons.timingBegin = (struct timespec){ 0 };
}
static void gltouchjoy_setTouchButtonValues(char char0, char char1, char charBoth) {
buttons.char0 = char0;
buttons.char1 = char1;
buttons.charBoth = charBoth;
}
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;
static void gltouchjoy_setTouchButtonTypes(touchjoy_button_type_t touchDownButton, touchjoy_button_type_t northButton, touchjoy_button_type_t southButton) {
buttons.touchDownButton = touchDownButton;
buttons.southButton = southButton;
buttons.northButton = northButton;
buttons.southChar = southChar;
buttons.southScancode = southScancode;
buttons.northChar = northChar;
buttons.northScancode = northScancode;
}
static void gltouchjoy_setTapDelay(float secs) {
@ -751,23 +705,36 @@ static void gltouchjoy_setTouchAxisSensitivity(float multiplier) {
}
static void gltouchjoy_setButtonSwitchThreshold(int delta) {
buttons.switchThreshold = delta;
joyglobals.switchThreshold = delta;
}
static void gltouchjoy_setTouchVariant(touchjoy_variant_t axisType) {
axes.type = axisType;
_setup_axis_object(axes.model);
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();
}
static touchjoy_variant_t gltouchjoy_getTouchVariant(void) {
return axes.type;
return variant.curr->variant();
}
static void gltouchjoy_setTouchAxisValues(char north, char west, char east, char south) {
axes.northChar = north;
axes.westChar = west;
axes.eastChar = east;
axes.southChar = south;
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);
}
@ -791,6 +758,10 @@ static void gltouchjoy_endCalibration(void) {
joyglobals.isCalibrating = false;
}
static bool gltouchjoy_isCalibrating(void) {
return joyglobals.isCalibrating;
}
__attribute__((constructor(CTOR_PRIORITY_LATE)))
static void _init_gltouchjoy(void) {
LOG("Registering OpenGL software touch joystick");
@ -799,25 +770,42 @@ static void _init_gltouchjoy(void) {
axes.centerY = 160;
axes.multiplier = 1.f;
axes.trackingIndex = TOUCH_NONE;
axes.type = EMULATED_JOYSTICK;
axes.northChar = MOUSETEXT_UP;
axes.westChar = MOUSETEXT_LEFT;
axes.eastChar = MOUSETEXT_RIGHT;
axes.southChar = MOUSETEXT_DOWN;
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.touchDownButton = TOUCH_BUTTON0;
buttons.southButton = TOUCH_BUTTON1;
buttons.northButton = TOUCH_BOTH;
buttons.char0 = MOUSETEXT_OPENAPPLE;
buttons.char1 = MOUSETEXT_CLOSEDAPPLE;
buttons.charBoth = '+';
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.switchThreshold = BUTTON_SWITCH_THRESHOLD_DEFAULT;
buttons.tapDelayThreadId = 0;
buttons.tapDelayMutex = (pthread_mutex_t){ 0 };
@ -828,6 +816,7 @@ static void _init_gltouchjoy(void) {
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;
@ -836,18 +825,18 @@ static void _init_gltouchjoy(void) {
joydriver_setTouchJoystickEnabled = &gltouchjoy_setTouchJoystickEnabled;
joydriver_setTouchJoystickOwnsScreen = &gltouchjoy_setTouchJoystickOwnsScreen;
joydriver_ownsScreen = &gltouchjoy_ownsScreen;
joydriver_setTouchButtonValues = &gltouchjoy_setTouchButtonValues;
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_setTouchAxisValues = &gltouchjoy_setTouchAxisValues;
joydriver_setTouchAxisTypes = &gltouchjoy_setTouchAxisTypes;
joydriver_setScreenDivision = &gltouchjoy_setScreenDivision;
joydriver_setAxisOnLeft = &gltouchjoy_setAxisOnLeft;
joydriver_beginCalibration = &gltouchjoy_beginCalibration;
joydriver_endCalibration = &gltouchjoy_endCalibration;
joydriver_isCalibrating = &gltouchjoy_isCalibrating;
glnode_registerNode(RENDER_LOW, (GLNode){
.setup = &gltouchjoy_setup,
@ -858,6 +847,25 @@ static void _init_gltouchjoy(void) {
});
}
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;
}
}
void gldriver_joystick_reset(void) {
#warning FIXME TODO expunge this olde API ...
}

99
src/video/gltouchjoy.h Normal file
View File

@ -0,0 +1,99 @@
/*
* 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.
*
*/
#ifndef _GLTOUCHJOY_H_
#define _GLTOUCHJOY_H_
#include "common.h"
#include "video/glvideo.h"
#include "video/glhudmodel.h"
#include "video/glnode.h"
// globals
typedef struct GLTouchJoyGlobals {
bool isAvailable; // Were there any OpenGL/memory errors on gltouchjoy initialization?
bool isShuttingDown;
bool isCalibrating; // Are we running in calibration mode?
bool isEnabled; // Does player want touchjoy enabled?
bool ownsScreen; // Does the touchjoy currently own the screen?
float minAlphaWhenOwnsScreen;
float minAlpha;
float screenDivider;
bool axisIsOnLeft;
int switchThreshold;
} GLTouchJoyGlobals;
extern GLTouchJoyGlobals joyglobals;
// touch axis variables
typedef struct GLTouchJoyAxes {
GLModel *model;
bool modelDirty;
uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS];
int rosetteScancodes[ROSETTE_ROWS * ROSETTE_COLS];
int centerX;
int centerY;
float multiplier;
int trackingIndex;
struct timespec timingBegin;
} GLTouchJoyAxes;
extern GLTouchJoyAxes axes;
// button object variables
typedef struct GLTouchJoyButtons {
GLModel *model;
uint8_t activeChar;
bool modelDirty;
touchjoy_button_type_t touchDownChar;
int touchDownScancode;
touchjoy_button_type_t northChar;
int northScancode;
touchjoy_button_type_t southChar;
int southScancode;
int centerX;
int centerY;
int trackingIndex;
struct timespec timingBegin;
pthread_t tapDelayThreadId;
pthread_mutex_t tapDelayMutex;
pthread_cond_t tapDelayCond;
unsigned int tapDelayNanos;
} GLTouchJoyButtons;
extern GLTouchJoyButtons buttons;
typedef struct GLTouchJoyVariant {
touchjoy_variant_t (*variant)(void);
void (*resetState)(void);
void (*setCurrButtonValue)(touchjoy_button_type_t theButtonChar, int theButtonScancode);
uint8_t (*buttonPress)(void);
void (*buttonRelease)(void);
void (*axisDown)(void);
void (*axisMove)(int dx, int dy);
void (*axisUp)(int dx, int dy);
} GLTouchJoyVariant;
// registers a touch joystick variant with manager
void gltouchjoy_registerVariant(touchjoy_variant_t variant, GLTouchJoyVariant *touchJoyVariant);
#endif // whole file

137
src/video/gltouchjoy_joy.c Normal file
View File

@ -0,0 +1,137 @@
/*
* 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
static GLTouchJoyVariant happyHappyJoyJoy = { 0 };
static struct {
uint8_t currJoyButtonValue0;
uint8_t currJoyButtonValue1;
uint8_t currButtonDisplayChar;
} joys;
static touchjoy_variant_t touchjoy_variant(void) {
return EMULATED_JOYSTICK;
}
static inline void _reset_axis_state(void) {
joy_x = HALF_JOY_RANGE;
joy_y = HALF_JOY_RANGE;
}
static inline void _reset_buttons_state(void) {
joy_button0 = 0x0;
joy_button1 = 0x0;
joys.currJoyButtonValue0 = 0;
joys.currJoyButtonValue1 = 0;
joys.currButtonDisplayChar = ' ';
}
static void touchjoy_resetState(void) {
_reset_axis_state();
_reset_buttons_state();
}
// ----------------------------------------------------------------------------
// axis state
static void touchjoy_axisDown(void) {
_reset_axis_state();
}
static void touchjoy_axisMove(int x, int y) {
if (axes.multiplier != 1.f) {
x = (int) ((float)x * axes.multiplier);
y = (int) ((float)y * axes.multiplier);
}
x += 0x80;
y += 0x80;
if (x < 0) {
x = 0;
}
if (x > 0xff) {
x = 0xff;
}
if (y < 0) {
y = 0;
}
if (y > 0xff) {
y = 0xff;
}
joy_x = x;
joy_y = y;
}
static void touchjoy_axisUp(int x, int y) {
_reset_axis_state();
}
// ----------------------------------------------------------------------------
// button state
static void touchjoy_setCurrButtonValue(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
if (theButtonChar == TOUCH_BUTTON0) {
joys.currJoyButtonValue0 = 0x80;
joys.currJoyButtonValue1 = 0;
joys.currButtonDisplayChar = MOUSETEXT_OPENAPPLE;
} else if (theButtonChar == TOUCH_BUTTON1) {
joys.currJoyButtonValue0 = 0;
joys.currJoyButtonValue1 = 0x80;
joys.currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE;
} else if (theButtonChar == TOUCH_BOTH) {
joys.currJoyButtonValue0 = 0x80;
joys.currJoyButtonValue1 = 0x80;
joys.currButtonDisplayChar = '+';
} else {
joys.currJoyButtonValue0 = 0;
joys.currJoyButtonValue1 = 0;
joys.currButtonDisplayChar = ' ';
}
}
static uint8_t touchjoy_buttonPress(void) {
joy_button0 = joys.currJoyButtonValue0;
joy_button1 = joys.currJoyButtonValue1;
return joys.currButtonDisplayChar;
}
static void touchjoy_buttonRelease(void) {
_reset_buttons_state();
}
// ----------------------------------------------------------------------------
__attribute__((constructor(CTOR_PRIORITY_EARLY)))
static void _init_gltouchjoy_joy(void) {
LOG("Registering OpenGL software touch joystick (joystick variant)");
happyHappyJoyJoy.variant = &touchjoy_variant,
happyHappyJoyJoy.resetState = &touchjoy_resetState,
happyHappyJoyJoy.setCurrButtonValue = &touchjoy_setCurrButtonValue,
happyHappyJoyJoy.buttonPress = &touchjoy_buttonPress,
happyHappyJoyJoy.buttonRelease = &touchjoy_buttonRelease,
happyHappyJoyJoy.axisDown = &touchjoy_axisDown,
happyHappyJoyJoy.axisMove = &touchjoy_axisMove,
happyHappyJoyJoy.axisUp = &touchjoy_axisUp,
gltouchjoy_registerVariant(EMULATED_JOYSTICK, &happyHappyJoyJoy);
}

421
src/video/gltouchjoy_kpad.c Normal file
View File

@ -0,0 +1,421 @@
/*
* 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
#define KEY_REPEAT_THRESHOLD_NANOS (NANOSECONDS_PER_SECOND / 4)
// WARNING : this does not match the rosette left-right-down layout, but what is returned by atan2f()
typedef enum keypad_octant_t {
OCTANT_WEST = 0,
OCTANT_NORTHWEST,
OCTANT_NORTH,
OCTANT_NORTHEAST,
OCTANT_EAST,
OCTANT_SOUTHEAST,
OCTANT_SOUTH,
OCTANT_SOUTHWEST,
ORIGIN,
} keypad_octant_t;
// Assuming radian value between -PI <-> PI
#define RADIANS_NORTHWEST ((-3.f * M_PI) / 4.f)
#define RADIANS_NORTH ((-1.f * M_PI) / 2.f)
#define RADIANS_NORTHEAST ((-1.f * M_PI) / 4.f)
#define RADIANS_SOUTHWEST (( 3.f * M_PI) / 4.f)
#define RADIANS_SOUTH (( 1.f * M_PI) / 2.f)
#define RADIANS_SOUTHEAST (( 1.f * M_PI) / 4.f)
typedef enum keypad_fire_t {
REPEAT_AXIS = 0,
REPEAT_AXIS_ALT,
REPEAT_BUTTON,
MAX_REPEATING,
} keypad_fire_t;
static struct {
keypad_octant_t axisCurrentOctant;
uint8_t currButtonDisplayChar;
// scancodes to fire upon axisUp, buttonPress, or as key-repeat
keypad_fire_t fireIdx;
int scancodes[MAX_REPEATING];
struct timespec timingBegins[MAX_REPEATING];
bool axisTiming;
bool buttonTiming;
} kpad = { 0 };
static GLTouchJoyVariant kpadJoy = { 0 };
// ----------------------------------------------------------------------------
// repeat key callback
static void touchkpad_keyboardReadCallback(void) {
assert(pthread_self() == cpu_thread_id);
struct timespec now = { 0 };
clock_gettime(CLOCK_MONOTONIC, &now);
for (unsigned int i=0; i<MAX_REPEATING; i++) {
bool fired = false;
int scancode = kpad.scancodes[kpad.fireIdx];
if (scancode >= 0) {
struct timespec deltat = timespec_diff(kpad.timingBegins[kpad.fireIdx], now, NULL);
if (deltat.tv_sec || deltat.tv_nsec > KEY_REPEAT_THRESHOLD_NANOS) {
LOG("REPEAT #%d/%lu/%lu: %d", kpad.fireIdx, deltat.tv_sec, deltat.tv_nsec, scancode);
c_keys_handle_input(scancode, /*pressed:*/true, /*ASCII:*/false);
fired = true;
}
}
++kpad.fireIdx;
if (kpad.fireIdx >= MAX_REPEATING) {
kpad.fireIdx = 0;
}
if (fired) {
break;
}
}
if (!kpad.axisTiming && !kpad.buttonTiming) {
keydriver_keyboardReadCallback = NULL;// unschedule callback
}
}
// ----------------------------------------------------------------------------
static touchjoy_variant_t touchkpad_variant(void) {
return EMULATED_KEYPAD;
}
static void touchkpad_resetState(void) {
kpad.axisCurrentOctant = ORIGIN;
for (unsigned int i=0; i<MAX_REPEATING; i++) {
kpad.scancodes[i] = -1;
}
kpad.axisTiming = false;
kpad.buttonTiming = false;
kpad.currButtonDisplayChar = ' ';
for (unsigned int i=0; i<ROSETTE_COLS; i++) {
for (unsigned int j=0; j<ROSETTE_ROWS; j++) {
c_keys_handle_input(axes.rosetteScancodes[i], /*pressed:*/false, /*ASCII:*/false);
}
}
c_keys_handle_input(buttons.touchDownScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(buttons.northScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(buttons.southScancode, /*pressed:*/false, /*ASCII:*/false);
}
// ----------------------------------------------------------------------------
// axis key(s) state
static void touchkpad_axisDown(void) {
kpad.axisCurrentOctant = ORIGIN;
if (axes.rosetteScancodes[ROSETTE_CENTER] >= 0) {
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_CENTER];
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_AXIS]);
kpad.axisTiming = true;
keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback;
}
}
static void touchkpad_axisMove(int dx, int dy) {
if ((dx > -joyglobals.switchThreshold) && (dx < joyglobals.switchThreshold) && (dy > -joyglobals.switchThreshold) && (dy < joyglobals.switchThreshold)) {
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_CENTER];
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
return;
}
if (dx == 0 && dy == 0) {
return;
}
do {
// determine the octant of this dx/dy
keypad_octant_t axisLastOctant = kpad.axisCurrentOctant;
const float radians = atan2f(dy, dx); // -180-180deg range
float radnorm = radians + M_PI; // 0-360deg range
if (UNLIKELY(radnorm < 0.f)) {
radnorm = 0.f; // clamp positive
}
float octant = radnorm + (M_PI/8.f); // rotate to correct octant (+45deg)
octant /= (M_PI/4.f); // divide to octant (/90deg)
kpad.axisCurrentOctant = (keypad_octant_t) ((int)octant & 0x7);// integer modulo maps to enum
if (kpad.axisCurrentOctant == axisLastOctant) {
break;
}
LOG("radians:%f radnorm:%f octant:%f, currOctant:%d", radians, radnorm, octant, kpad.axisCurrentOctant);
// handle a new octant
struct timespec now = { 0 };
clock_gettime(CLOCK_MONOTONIC, &now);
kpad.timingBegins[REPEAT_AXIS] = now;
kpad.timingBegins[REPEAT_AXIS_ALT] = now;
keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback;
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
switch (kpad.axisCurrentOctant) {
case OCTANT_NORTHWEST:
if (axes.rosetteScancodes[ROSETTE_NORTHWEST] >= 0) {
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHWEST];
LOG("XY : NORTHWEST, (%d)", axes.rosetteScancodes[ROSETTE_WEST]);
} else if (axes.rosetteScancodes[ROSETTE_NORTH] < 0) {
if (radians > RADIANS_NORTHWEST) {
LOG("IGNORING Y (NORTH) ...");
kpad.scancodes[REPEAT_AXIS] = -1;
} else {
LOG("X : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST];
}
} else if (axes.rosetteScancodes[ROSETTE_WEST] < 0) {
if (radians < RADIANS_NORTHWEST) {
LOG("IGNORING X (WEST) ...");
kpad.scancodes[REPEAT_AXIS] = -1;
} else {
LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH];
}
} else {
if (radians > RADIANS_NORTHWEST) {
LOG("XY : NORTH (%d) & WEST (%d)", axes.rosetteScancodes[ROSETTE_NORTH], axes.rosetteScancodes[ROSETTE_WEST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_WEST];
} else {
LOG("XY : WEST (%d) & NORTH (%d)", axes.rosetteScancodes[ROSETTE_WEST], axes.rosetteScancodes[ROSETTE_NORTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_NORTH];
}
}
break;
case OCTANT_NORTH:
LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH];
break;
case OCTANT_NORTHEAST:
if (axes.rosetteScancodes[ROSETTE_NORTHEAST] >= 0) {
LOG("XY : NORTHEAST (%d)", axes.rosetteScancodes[ROSETTE_NORTHEAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHEAST];
} else if (axes.rosetteScancodes[ROSETTE_NORTH] < 0) {
if (radians < RADIANS_NORTHEAST) {
LOG("IGNORING Y (NORTH) ...");
kpad.scancodes[REPEAT_AXIS] = -1;
} else {
LOG("X : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST];
}
} else if (axes.rosetteScancodes[ROSETTE_EAST] < 0) {
if (radians > RADIANS_NORTHEAST) {
LOG("IGNORING X (EAST) ...");
kpad.scancodes[REPEAT_AXIS] = -1;
} else {
LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH];
}
} else {
if (radians < RADIANS_NORTHEAST) {
LOG("XY : NORTH (%d) & EAST (%d)", axes.rosetteScancodes[ROSETTE_NORTH], axes.rosetteScancodes[ROSETTE_EAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_EAST];
} else {
LOG("XY : EAST (%d) & NORTH (%d)", axes.rosetteScancodes[ROSETTE_EAST], axes.rosetteScancodes[ROSETTE_NORTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_NORTH];
}
}
break;
case OCTANT_WEST:
LOG("Y : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST];
break;
case OCTANT_EAST:
LOG("Y : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST];
break;
case OCTANT_SOUTHWEST:
if (axes.rosetteScancodes[ROSETTE_SOUTHWEST] >= 0) {
LOG("XY : SOUTHWEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHWEST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHWEST];
} else if (axes.rosetteScancodes[ROSETTE_SOUTH] < 0) {
if (radians < RADIANS_SOUTHWEST) {
kpad.scancodes[REPEAT_AXIS] = -1;
LOG("IGNORING Y (SOUTH) ...");
} else {
LOG("X : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST];
}
} else if (axes.rosetteScancodes[ROSETTE_WEST] < 0) {
if (radians > RADIANS_SOUTHWEST) {
kpad.scancodes[REPEAT_AXIS] = -1;
LOG("IGNORING X (WEST) ...");
} else {
LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH];
}
} else {
if (radians < RADIANS_SOUTHWEST) {
LOG("XY : SOUTH (%d) & WEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTH], axes.rosetteScancodes[ROSETTE_WEST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_WEST];
} else {
LOG("XY : WEST (%d) & SOUTH (%d)", axes.rosetteScancodes[ROSETTE_WEST], axes.rosetteScancodes[ROSETTE_SOUTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_SOUTH];
}
}
break;
case OCTANT_SOUTH:
LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH];
break;
case OCTANT_SOUTHEAST:
if (axes.rosetteScancodes[ROSETTE_SOUTHEAST] >= 0) {
LOG("XY : SOUTHEAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHEAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHEAST];
} else if (axes.rosetteScancodes[ROSETTE_SOUTH] < 0) {
if (radians > RADIANS_SOUTHEAST) {
LOG("IGNORING Y (SOUTH) ...");
kpad.scancodes[REPEAT_AXIS] = -1;
} else {
LOG("X : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST];
}
} else if (axes.rosetteScancodes[ROSETTE_EAST] < 0) {
if (radians < RADIANS_SOUTHEAST) {
LOG("IGNORING X (EAST) ...");
kpad.scancodes[REPEAT_AXIS] = -1;
} else {
LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH];
}
} else {
if (radians > RADIANS_SOUTHEAST) {
LOG("XY : SOUTH (%d) & EAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTH], axes.rosetteScancodes[ROSETTE_EAST]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_EAST];
} else {
LOG("XY : EAST (%d) & SOUTH (%d)", axes.rosetteScancodes[ROSETTE_EAST], axes.rosetteScancodes[ROSETTE_SOUTH]);
kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST];
kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_SOUTH];
}
}
break;
default:
assert(false && "should not happen");
break;
}
} while (0);
kpad.axisTiming = true;
}
static void touchkpad_axisUp(int dx, int dy) {
touchkpad_axisMove(dx, dy);
kpad.axisCurrentOctant = ORIGIN;
int scancode = kpad.scancodes[REPEAT_AXIS];
kpad.scancodes[REPEAT_AXIS] = -1;
if (scancode < 0) {
scancode = kpad.scancodes[REPEAT_AXIS_ALT];
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
}
c_keys_handle_input(scancode, /*pressed:*/true, /*ASCII:*/false);
c_keys_handle_input(scancode, /*pressed:*/false, /*ASCII:*/false);
kpad.axisTiming = false;
// if no other scancodes, REPEAT_AXIS_ALT (if non-negative) will be fired once remaining in callback
}
// ----------------------------------------------------------------------------
// button key state
static void touchkpad_setCurrButtonValue(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
if (theButtonChar >= 0) {
kpad.currButtonDisplayChar = theButtonChar;
kpad.scancodes[REPEAT_BUTTON] = theButtonScancode;
} else {
kpad.currButtonDisplayChar = ' ';
kpad.scancodes[REPEAT_BUTTON] = -1;
}
}
static uint8_t touchkpad_buttonPress(void) {
if (kpad.scancodes[REPEAT_BUTTON] >= 0) {
LOG("->BUTT : %d/'%c'", kpad.scancodes[REPEAT_BUTTON], kpad.currButtonDisplayChar);
clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_BUTTON]);
c_keys_handle_input(kpad.scancodes[REPEAT_BUTTON], /*pressed:*/true, /*ASCII:*/false);
kpad.buttonTiming = true;
keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback;
}
return kpad.currButtonDisplayChar;
}
static void touchkpad_buttonRelease(void) {
kpad.scancodes[REPEAT_BUTTON] = -1;
kpad.buttonTiming = false;
c_keys_handle_input(kpad.scancodes[REPEAT_BUTTON], /*pressed:*/false, /*ASCII:*/false);
}
// ----------------------------------------------------------------------------
__attribute__((constructor(CTOR_PRIORITY_EARLY)))
static void _init_gltouchjoy_kpad(void) {
LOG("Registering OpenGL software touch joystick (keypad variant)");
for (unsigned int i=0; i<MAX_REPEATING; i++) {
kpad.scancodes[i] = -1;
}
kpad.currButtonDisplayChar = ' ';
kpadJoy.variant = &touchkpad_variant,
kpadJoy.resetState = &touchkpad_resetState,
kpadJoy.setCurrButtonValue = &touchkpad_setCurrButtonValue,
kpadJoy.buttonPress = &touchkpad_buttonPress,
kpadJoy.buttonRelease = &touchkpad_buttonRelease,
kpadJoy.axisDown = &touchkpad_axisDown,
kpadJoy.axisMove = &touchkpad_axisMove,
kpadJoy.axisUp = &touchkpad_axisUp,
gltouchjoy_registerVariant(EMULATED_KEYPAD, &kpadJoy);
}

View File

@ -143,7 +143,15 @@ GLUE_C_WRITE(write_unmapped_softswitch)
GLUE_C_READ(read_keyboard)
{
return apple_ii_64k[0][0xC000];
uint8_t b = apple_ii_64k[0][0xC000];
#if INTERFACE_TOUCH
// touch interface is expected to rate limit this callback by unregistering/NULLifying
void (*readCallback)(void) = keydriver_keyboardReadCallback;
if (readCallback) {
readCallback();
}
#endif
return b;
}
GLUE_C_READ(read_keyboard_strobe)