mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-12-24 18:31:51 +00:00
Change the button gesture recognizer state machine
- Use a synchronized event queue rather than overwriting "current" button state - Use aggressive assertions to ensure state machine is coherent
This commit is contained in:
parent
9c9ba6e7ef
commit
13184ec4be
@ -22,6 +22,13 @@
|
||||
# define TOUCH_JOY_LOG(...)
|
||||
#endif
|
||||
|
||||
#define DEBUG_TOUCH_JOY_GESTURE 0
|
||||
#if DEBUG_TOUCH_JOY_GESTURE
|
||||
# define TOUCH_JOY_GESTURE_LOG(...) LOG(__VA_ARGS__)
|
||||
#else
|
||||
# define TOUCH_JOY_GESTURE_LOG(...)
|
||||
#endif
|
||||
|
||||
#define ROSETTE_ROWS 3
|
||||
#define ROSETTE_COLS 3
|
||||
|
||||
|
@ -17,16 +17,21 @@
|
||||
|
||||
#define BUTTON_TAP_DELAY_NANOS_DEFAULT 50000000
|
||||
|
||||
static GLTouchJoyVariant happyHappyJoyJoy = { 0 };
|
||||
|
||||
static struct {
|
||||
typedef struct touch_event_s {
|
||||
struct touch_event_s *next;
|
||||
interface_touch_event_t event;
|
||||
uint8_t currJoyButtonValue0;
|
||||
uint8_t currJoyButtonValue1;
|
||||
uint8_t currButtonDisplayChar;
|
||||
} touch_event_s;
|
||||
|
||||
static touch_event_s *touchEventQ = NULL;
|
||||
|
||||
static GLTouchJoyVariant happyHappyJoyJoy = { 0 };
|
||||
|
||||
static struct {
|
||||
void (*buttonDrawCallback)(char newChar);
|
||||
|
||||
bool trackingButton;
|
||||
bool trackingButtonMove;
|
||||
pthread_t tapDelayThreadId;
|
||||
pthread_mutex_t tapDelayMutex;
|
||||
pthread_cond_t tapDelayCond;
|
||||
@ -51,9 +56,6 @@ static inline void _reset_axis_state(void) {
|
||||
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) {
|
||||
@ -63,12 +65,19 @@ static void touchjoy_resetState(void) {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Tap Delay Thread : delays processing of touch-down so that a different joystick button can be fired
|
||||
// Tap Delay Thread : implements a gesture recognizer that differentiates between a "long touch", "tap", and "swipe
|
||||
// down/up" gestures. Necessarily delays processing of initial touch down event to make a proper determination. Also
|
||||
// delays resetting joystick button state after touch up event to avoid resetting joystick button state too soon.
|
||||
//
|
||||
// * long touch and tap are interpreted as one (configurable) joystick button fire event
|
||||
// * swipe up and swipe down are the other two (configurable) joystick button fire events
|
||||
//
|
||||
|
||||
static inline void _signal_tap_delay(void) {
|
||||
pthread_mutex_lock(&joys.tapDelayMutex);
|
||||
pthread_cond_signal(&joys.tapDelayCond);
|
||||
pthread_mutex_unlock(&joys.tapDelayMutex);
|
||||
static struct timespec *_tap_wait(void) {
|
||||
static struct timespec wait = { 0 };
|
||||
clock_gettime(CLOCK_REALTIME, &wait);
|
||||
wait = timespec_add(wait, joys.tapDelayNanos);
|
||||
return &wait;
|
||||
}
|
||||
|
||||
static void *_button_tap_delayed_thread(void *dummyptr) {
|
||||
@ -76,65 +85,98 @@ static void *_button_tap_delayed_thread(void *dummyptr) {
|
||||
|
||||
pthread_mutex_lock(&joys.tapDelayMutex);
|
||||
|
||||
bool deepSleep = false;
|
||||
uint8_t currJoyButtonValue0 = 0x0;
|
||||
uint8_t currJoyButtonValue1 = 0x0;
|
||||
uint8_t currButtonDisplayChar = ' ';
|
||||
int timedOut = ETIMEDOUT;
|
||||
for (;;) {
|
||||
if (UNLIKELY(!joyglobals.isAvailable)) {
|
||||
break;
|
||||
}
|
||||
|
||||
struct timespec wait;
|
||||
clock_gettime(CLOCK_REALTIME, &wait);
|
||||
wait = timespec_add(wait, joys.tapDelayNanos);
|
||||
int timedOut = pthread_cond_timedwait(&joys.tapDelayCond, &joys.tapDelayMutex, &wait); // wait and possibly consume event
|
||||
assert((!timedOut || timedOut == ETIMEDOUT) && "should not fail any other way");
|
||||
if (timedOut) {
|
||||
// reset state and deep sleep waiting for touch down
|
||||
_reset_buttons_state();
|
||||
TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] deep sleep ...");
|
||||
pthread_cond_wait(&joys.tapDelayCond, &joys.tapDelayMutex);
|
||||
} else {
|
||||
// delays reset of button state while remaining ready to process a touch down
|
||||
TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] event looping ...");
|
||||
timedOut = pthread_cond_timedwait(&joys.tapDelayCond, &joys.tapDelayMutex, _tap_wait()); // wait and possibly consume event
|
||||
assert((!timedOut || timedOut == ETIMEDOUT) && "should not fail any other way");
|
||||
if (timedOut) {
|
||||
// reset state and go into deep sleep
|
||||
continue;
|
||||
}
|
||||
_reset_buttons_state();
|
||||
}
|
||||
|
||||
if (!timedOut) {
|
||||
if (!deepSleep) {
|
||||
if (joys.trackingButtonMove) {
|
||||
// dragging
|
||||
currJoyButtonValue0 = 0x0;
|
||||
currJoyButtonValue1 = 0x0;
|
||||
currButtonDisplayChar = joys.currButtonDisplayChar;
|
||||
} else if (joys.trackingButton) {
|
||||
// touch down -- delay consumption to determine if tap or drag
|
||||
currJoyButtonValue0 = joys.currJoyButtonValue0;
|
||||
currJoyButtonValue1 = joys.currJoyButtonValue1;
|
||||
currButtonDisplayChar = joys.currButtonDisplayChar;
|
||||
joys.buttonDrawCallback(currButtonDisplayChar);
|
||||
// zero the buttons before delay
|
||||
_reset_buttons_state();
|
||||
continue;
|
||||
} else {
|
||||
// touch up becomes tap
|
||||
joys.currJoyButtonValue0 = currJoyButtonValue0;
|
||||
joys.currJoyButtonValue1 = currJoyButtonValue1;
|
||||
joys.currButtonDisplayChar = currButtonDisplayChar;
|
||||
}
|
||||
if (UNLIKELY(!joyglobals.isAvailable)) {
|
||||
break;
|
||||
}
|
||||
|
||||
TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] touch down ...");
|
||||
|
||||
touch_event_s *touchCurrEvent = NULL;
|
||||
touch_event_s *touchPrevEvent = touchEventQ;
|
||||
assert(touchPrevEvent && "should be a touch event ready to consume");
|
||||
touchEventQ = touchEventQ->next;
|
||||
assert(touchPrevEvent->event == TOUCH_DOWN && "event queue head should be a touch down event");
|
||||
|
||||
for (;;) {
|
||||
// delay processing of touch down to perform simple gesture recognition
|
||||
timedOut = pthread_cond_timedwait(&joys.tapDelayCond, &joys.tapDelayMutex, _tap_wait());
|
||||
assert((!timedOut || timedOut == ETIMEDOUT) && "should not fail any other way");
|
||||
|
||||
if (UNLIKELY(!joyglobals.isAvailable)) {
|
||||
break;
|
||||
}
|
||||
|
||||
touchCurrEvent = touchEventQ;
|
||||
if (!touchCurrEvent) {
|
||||
assert(timedOut);
|
||||
// touch-down-and-hold
|
||||
TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] long touch ...");
|
||||
joy_button0 = touchPrevEvent->currJoyButtonValue0;
|
||||
joy_button1 = touchPrevEvent->currJoyButtonValue1;
|
||||
joys.buttonDrawCallback(touchPrevEvent->currButtonDisplayChar);
|
||||
continue;
|
||||
}
|
||||
touchEventQ = touchEventQ->next;
|
||||
|
||||
if (touchCurrEvent->event == TOUCH_MOVE) {
|
||||
// dragging ...
|
||||
TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] move ...");
|
||||
joy_button0 = touchCurrEvent->currJoyButtonValue0;
|
||||
joy_button1 = touchCurrEvent->currJoyButtonValue1;
|
||||
joys.buttonDrawCallback(touchCurrEvent->currButtonDisplayChar);
|
||||
FREE(touchPrevEvent);
|
||||
touchPrevEvent = touchCurrEvent;
|
||||
} else if (touchCurrEvent->event == TOUCH_UP) {
|
||||
// tap
|
||||
TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] touch up ...");
|
||||
joy_button0 = touchPrevEvent->currJoyButtonValue0;
|
||||
joy_button1 = touchPrevEvent->currJoyButtonValue1;
|
||||
joys.buttonDrawCallback(touchPrevEvent->currButtonDisplayChar);
|
||||
timedOut = 0;
|
||||
break;
|
||||
} else {
|
||||
LOG("OOPS : unexpected touch event : %d", touchCurrEvent->event);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
joy_button0 = joys.currJoyButtonValue0;
|
||||
joy_button1 = joys.currJoyButtonValue1;
|
||||
joys.buttonDrawCallback(joys.currButtonDisplayChar);
|
||||
FREE(touchPrevEvent);
|
||||
FREE(touchCurrEvent);
|
||||
}
|
||||
|
||||
deepSleep = false;
|
||||
if (timedOut && !joys.trackingButton) {
|
||||
deepSleep = true;
|
||||
_reset_buttons_state();
|
||||
TOUCH_JOY_LOG(">>> [DELAYEDTAP] end ...");
|
||||
pthread_cond_wait(&joys.tapDelayCond, &joys.tapDelayMutex); // consume initial touch down
|
||||
TOUCH_JOY_LOG(">>> [DELAYEDTAP] begin ...");
|
||||
} else {
|
||||
TOUCH_JOY_LOG(">>> [DELAYEDTAP] event looping ...");
|
||||
}
|
||||
// clear out event queue
|
||||
touch_event_s *p = touchEventQ;
|
||||
while (p) {
|
||||
touch_event_s *dead = p;
|
||||
p = p->next;
|
||||
FREE(dead);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&joys.tapDelayMutex);
|
||||
|
||||
joys.tapDelayThreadId = 0;
|
||||
joys.tapDelayMutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
|
||||
|
||||
@ -145,15 +187,23 @@ static void *_button_tap_delayed_thread(void *dummyptr) {
|
||||
|
||||
static void touchjoy_setup(void (*buttonDrawCallback)(char newChar)) {
|
||||
assert((joys.tapDelayThreadId == 0) && "setup called multiple times!");
|
||||
joys.buttonDrawCallback = buttonDrawCallback;
|
||||
pthread_create(&joys.tapDelayThreadId, NULL, (void *)&_button_tap_delayed_thread, (void *)NULL);
|
||||
|
||||
long lVal = 0;
|
||||
const interface_device_t screenOwner = prefs_parseLongValue(PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, &lVal, /*base:*/10) ? lVal : TOUCH_DEVICE_NONE;
|
||||
if (screenOwner == TOUCH_DEVICE_JOYSTICK) {
|
||||
joys.buttonDrawCallback = buttonDrawCallback;
|
||||
pthread_create(&joys.tapDelayThreadId, NULL, (void *)&_button_tap_delayed_thread, (void *)NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void touchjoy_shutdown(void) {
|
||||
pthread_mutex_lock(&joys.tapDelayMutex);
|
||||
pthread_cond_signal(&joys.tapDelayCond);
|
||||
if (pthread_join(joys.tapDelayThreadId, NULL)) {
|
||||
pthread_mutex_unlock(&joys.tapDelayMutex);
|
||||
if (joys.tapDelayThreadId && pthread_join(joys.tapDelayThreadId, NULL)) {
|
||||
ERRLOG("OOPS: pthread_join tap delay thread ...");
|
||||
}
|
||||
joys.tapDelayThreadId = 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -196,30 +246,46 @@ static void touchjoy_axisUp(int x, int y) {
|
||||
// ----------------------------------------------------------------------------
|
||||
// button state
|
||||
|
||||
static void _set_current_button_state(touchjoy_button_type_t theButtonChar) {
|
||||
static void _signal_tap_delay_event(interface_touch_event_t type, touchjoy_button_type_t theButtonChar) {
|
||||
touch_event_s *touchEvent = MALLOC(sizeof(*touchEvent));
|
||||
touchEvent->next = NULL;
|
||||
touchEvent->event = type;
|
||||
if (theButtonChar == TOUCH_BUTTON1) {
|
||||
joys.currJoyButtonValue0 = 0x80;
|
||||
joys.currJoyButtonValue1 = 0;
|
||||
joys.currButtonDisplayChar = MOUSETEXT_OPENAPPLE;
|
||||
touchEvent->currJoyButtonValue0 = 0x80;
|
||||
touchEvent->currJoyButtonValue1 = 0;
|
||||
touchEvent->currButtonDisplayChar = MOUSETEXT_OPENAPPLE;
|
||||
} else if (theButtonChar == TOUCH_BUTTON2) {
|
||||
joys.currJoyButtonValue0 = 0;
|
||||
joys.currJoyButtonValue1 = 0x80;
|
||||
joys.currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE;
|
||||
touchEvent->currJoyButtonValue0 = 0;
|
||||
touchEvent->currJoyButtonValue1 = 0x80;
|
||||
touchEvent->currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE;
|
||||
} else if (theButtonChar == TOUCH_BOTH) {
|
||||
joys.currJoyButtonValue0 = 0x80;
|
||||
joys.currJoyButtonValue1 = 0x80;
|
||||
joys.currButtonDisplayChar = '+';
|
||||
touchEvent->currJoyButtonValue0 = 0x80;
|
||||
touchEvent->currJoyButtonValue1 = 0x80;
|
||||
touchEvent->currButtonDisplayChar = '+';
|
||||
} else {
|
||||
joys.currJoyButtonValue0 = 0;
|
||||
joys.currJoyButtonValue1 = 0;
|
||||
joys.currButtonDisplayChar = ' ';
|
||||
touchEvent->currJoyButtonValue0 = 0;
|
||||
touchEvent->currJoyButtonValue1 = 0;
|
||||
touchEvent->currButtonDisplayChar = ' ';
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&joys.tapDelayMutex);
|
||||
touch_event_s *p0 = NULL;
|
||||
touch_event_s *p = touchEventQ;
|
||||
while (p) {
|
||||
p0 = p;
|
||||
p = p->next;
|
||||
}
|
||||
if (p0) {
|
||||
p0->next = touchEvent;
|
||||
} else {
|
||||
touchEventQ = touchEvent;
|
||||
}
|
||||
pthread_cond_signal(&joys.tapDelayCond);
|
||||
pthread_mutex_unlock(&joys.tapDelayMutex);
|
||||
}
|
||||
|
||||
static void touchjoy_buttonDown(void) {
|
||||
_set_current_button_state(joys.touchDownChar);
|
||||
joys.trackingButton = true;
|
||||
_signal_tap_delay();
|
||||
_signal_tap_delay_event(TOUCH_DOWN, joys.touchDownChar);
|
||||
}
|
||||
|
||||
static void touchjoy_buttonMove(int dx, int dy) {
|
||||
@ -232,16 +298,12 @@ static void touchjoy_buttonMove(int dx, int dy) {
|
||||
theButtonChar = joys.southChar;
|
||||
}
|
||||
|
||||
_set_current_button_state(theButtonChar);
|
||||
_signal_tap_delay();
|
||||
_signal_tap_delay_event(TOUCH_MOVE, theButtonChar);
|
||||
}
|
||||
joys.trackingButtonMove = true;
|
||||
}
|
||||
|
||||
static void touchjoy_buttonUp(int dx, int dy) {
|
||||
joys.trackingButton = false;
|
||||
joys.trackingButtonMove = false;
|
||||
_signal_tap_delay();
|
||||
_signal_tap_delay_event(TOUCH_UP, ' ');
|
||||
}
|
||||
|
||||
static void touchjoy_prefsChanged(const char *domain) {
|
||||
|
Loading…
Reference in New Issue
Block a user