Updated: October 28, 2024 |
This tutorial shows you the basics for defining a custom gesture recognizer.
The definition of your custom gesture recognizer includes the following:
typedef struct { unsigned max_displacement; /** The maximum distance your finger can move before this custom gesture fails. */ unsigned max_hold_ms; /** The maximum time your finger can remain touching the screen before this custom gesture fails. */ unsigned max_delay_ms; /** The time between the first release and the second touch. */ } custom_gesture_params_t;
typedef enum { CUSTOM_STATE_INIT = 0, CUSTOM_STATE_FIRST_TOUCH, CUSTOM_STATE_FIRST_RELEASE, CUSTOM_STATE_SECOND_TOUCH, CUSTOM_STATE_SECOND_RELEASE } custom_gesture_state_e;
typedef struct { gesture_base_t base; /* The gesture base data structure. */ custom_gesture_params_t params; /** Your custom gesture recognizer parameters. */ gesture_coords_t first_touch; /** The coordinates of the first touch. */ gesture_coords_t first_release; /** The coordinates of the first release. */ gesture_coords_t second_touch; /** The coordinates of the second touch. */ gesture_coords_t second_release; /** The coordinates of the second release. */ custom_gesture_state_e ct_state; /** The intermediate state of your recognizer. */ int timer; /** The ID of the timer for this gesture. */ } gesture_custom_t;
If your custom gesture recognizer is timer-based, you need to implement a callback function that will be called upon receipt of a timer event. Your gesture_timer_callback_t function returns the new, or unchanged, state based on the timer event received and might look like this:
static gesture_state_e custom_gesture_timer_callback(gesture_base_t* base, void* param) { return GESTURE_STATE_FAILED; }
Here's an example of what your custom gesture recognizer alloc() might look like:
gesture_custom_t* custom_gesture_alloc(custom_gesture_params_t* params, gesture_callback_f callback, struct gestures_set* set) { gesture_custom_t* user_gesture = calloc(1, sizeof(*user_gesture)); if (NULL == user_gesture) { return NULL; } gesture_base_init(&user_gesture->base); gestures_set_add(set, &user_gesture->base); user_gesture->base.type = GESTURE_USER; user_gesture->base.funcs.free = user_gesture_gesture_free; user_gesture->base.funcs.process_event = user_gesture_gesture_process_event; user_gesture->base.funcs.reset = user_gesture_gesture_reset; user_gesture->base.callback = callback; user_gesture->timer = gesture_timer_create(&user_gesture->base, custom_gesture_timer_callback, NULL); user_gesture_gesture_reset(&user_gesture->base); if (NULL != params) { user_gesture->params = *params; } else { user_gesture_gesture_default_params(&user_gesture->params); } return user_gesture; }
You need to implement a process_event() function that's responsible for state-handling and returning the new (or unchanged) gesture set:
gesture_state_e (*process_event)(struct contact_id_map* map, struct gesture_base* gesture, mtouch_event_t* event, int* consumed);
Ensure that your state transitions are valid according to the Gestures library. Refer to State transitions.
gesture_state_e custom_gesture_gesture_process_event(struct contact_id_map* map, gesture_base_t* gesture, mtouch_event_t* event, int* consumed) { gesture_custom_t* custom_gesture = (gesture_custom_gesture_t*)gesture; gesture_coords_t coords; gesture_coords_t* compare_coords; int set_id = map_contact_id(map, event->contact_id); if (set_id < 0) { error("process_event() called with event with invalid contact_id"); goto failed; } switch (user_gesture->base.state) { case GESTURE_STATE_UNRECOGNIZED: switch (event->event_type) { case INPUT_EVENT_MTOUCH_TOUCH: if (set_id > 0) { goto failed; } else if (user_gesture->ct_state > CUSTOM_STATE_FIRST_TOUCH) { gesture_timer_clear(gesture, user_gesture->fail_timer); save_coords(event, &user_gesture->second_touch); if (!((max_displacement_abs(&user_gesture->first_release, &user_gesture->second_touch) <= user_gesture->params.max_displacement) && (diff_time_ms(&user_gesture->first_release, &user_gesture->second_touch) <= user_gesture->params.max_delay_ms))) { goto failed; } user_gesture->ct_state = CUSTOM_STATE_SECOND_TOUCH; } else { save_coords(event, &user_gesture->first_touch); user_gesture->ct_state = CUSTOM_STATE_FIRST_TOUCH; } goto nochange; case INPUT_EVENT_MTOUCH_MOVE: save_coords(event, &coords); if (user_gesture->ct_state >= CUSTOM_STATE_SECOND_TOUCH) { compare_coords = &user_gesture->second_touch; } else { compare_coords = &user_gesture->first_touch; } if (!((max_displacement_abs(compare_coords, &coords) <= user_gesture->params.max_displacement) && (diff_time_ms(compare_coords, &coords) <= user_gesture->params.max_hold_ms))) { goto failed; } goto nochange; case INPUT_EVENT_MTOUCH_RELEASE: if (user_gesture->ct_state >= CUSTOM_STATE_SECOND_TOUCH) { save_coords(event, &user_gesture->second_release); if (!((max_displacement_abs(&user_gesture->second_touch, &user_gesture->second_release) <= user_gesture->params.max_displacement) && (diff_time_ms(&user_gesture->second_touch, &user_gesture->second_release) <= user_gesture->params.max_hold_ms))) { goto failed; } user_gesture->ct_state = CUSTOM_STATE_SECOND_RELEASE; goto complete; } else { save_coords(event, &user_gesture->first_release); if (!((max_displacement_abs(&user_gesture->first_touch, &user_gesture->first_release) <= user_gesture->params.max_displacement) && (diff_time_ms(&user_gesture->first_touch, &user_gesture->first_release) <= user_gesture->params.max_hold_ms))) { goto failed; } user_gesture->ct_state = CUSTOM_STATE_FIRST_RELEASE; /* Set a timer in case an event doesn't come in */ gesture_timer_set_event(gesture, user_gesture->timer, user_gesture->params.max_delay_ms, event); } goto nochange; default: warn("Unhandled switch/case: %d", event->event_type); goto failed; } case GESTURE_STATE_RECOGNIZED: error("GESTURE_STATE_RECOGNIZED is an invalid state for double tap"); break; case GESTURE_STATE_UPDATING: error("GESTURE_STATE_UPDATING is an invalid state for double tap"); break; case GESTURE_STATE_COMPLETE: error("process_event() called on complete gesture"); break; case GESTURE_STATE_FAILED: error("process_event() called on failed gesture"); break; case GESTURE_STATE_NONE: error("process_event() called on uninitialized gesture"); break; } nochange: *consumed = 0; return user_gesture->base.state; failed: *consumed = 0; return GESTURE_STATE_FAILED; complete: *consumed = 1; return GESTURE_STATE_COMPLETE; }
Release the memory that's associated with the custom gesture:
void custom_gesture_free(struct gesture_base* gesture);
A free() function for a custom gesture may simply look like this:
void custom_gesture_free(gesture_base_t* gesture) { free(gesture); }
Reset any specific data structures that are associated with the custom gesture:
void custom_gesture_reset(struct gesture_base* gesture);
A reset() function for a custom gesture may be empty if there are no specific data structures associated with your custom gesture.
void custom_gesture_reset(gesture_base_t* gesture) { gesture_custom_t* user_gesture = (gesture_custom_t*)gesture; user_gesture->ct_state = CUSTOM_STATE_INIT; }