Tutorial: Create a custom gesture

This tutorial shows you the basics for defining a custom gesture recognizer.

You will learn to:

  • Define a custom gesture recognizer
  • Create a timer callback function
  • Create an alloc() function
  • Create a process_event() function
  • Create a free() function
  • Create a reset() function

Define your custom gesture recognizer

The definition of your custom gesture recognizer includes the following:

Custom gesture recognizer parameters (optional)
A structure that represents the parameters specific to your custom gesture recognizer. This structure is optional. If your custom gesture recognizer has no specific parameters, then you don't need to define this structure. Here's an example of what parameters might be in this custom gesture-recognizer structure:
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;
Custom gesture-recognizer states (optional)
Constants that represent the states specific to your custom gesture recognizer; these states are in addition to the set of states defined in gesture_state_e. Additional states for your gesture recognizer are optional. If your gesture recognizer doesn't need additional states, these constants aren't necessary. Here's an example of what the definition of additional states might look like:
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;
Custom gesture-recognizer information
A structure that contains information about your custom gesture recognizer. This structure must list gesture_base_t as its first data member. Here's an example of what information might be included in your gesture recognizer:
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;

Create the timer callback function (optional)

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

Create the alloc() function

Your alloc() function must:
  1. Allocate the memory necessary for your custom gesture recognizer.
  2. Invoke gesture_base_init() to initialize the gesture base data structure.
  3. Invoke gesture_set_add() to add your custom gesture to the gesture set.
  4. Set the gesture recognizer type as GESTURE_USER.
  5. Set the process_event(), the reset(), and the free() functions.
  6. Set the gesture callback: callback.
  7. Perform any custom gesture-specific initialization that isn't part of the reset.
  8. Store the custom gesture-specific parameters with the gesture recognizer, and set default parameters. If your custom gesture recognizer uses a timer, use gesture_timer_create() to obtain the ID for your timer.

If your custom gesture recognizer uses timers, then use gesture_timer_create() to create the timer and register your timer callback with your gesture recognizer.

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

Create the process_event() function

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.

A process_event() function for a custom gesture may look like this:
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;
}

Create the free() function

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

Create the reset() function

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

Complete code for the custom gesture app

Now that you have completed the tutorial, you have written code for a custom gesture. You can continue to customize your custom gesture. You can click custom_gesture.h and custom_gesture.c to see the complete code discussed in this tutorial.

#include "gestures/types.h"

/* The stucture custom_gesture_params_t represents the parameters for the custom gesture. */
typedef struct {
   unsigned max_displacement; /* The maximum distance the finger can move before
                                 the custom gesture fails. */
   unsigned max_hold_ms;      /* The maximum time the finger can remain touching
                                 the screen before the custom gesture fails. */
   unsigned max_delay_ms;     /* The time between the first release and the second touch. */
} custom_gesture_params_t;

/* The enumeration custom_gesture_state_e defines additional states the custom
 * gesture can transition between. */
typedef enum {
   CT_STATE_INIT = 0,
   CT_STATE_FIRST_TOUCH,
   CT_STATE_FIRST_RELEASE,
   CT_STATE_SECOND_TOUCH,
   CT_STATE_SECOND_RELEASE
} custom_gesture_state_e;

/* The structure gesture_custom_gesture_t carries data about the custom gesture. */
typedef struct {
   gesture_base_t base;             /* The gesture base data structure. */
   custom_gesture_params_t params;  /* The custom gesture 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 dt_state; /* The intermediate state of the custom gesture. */
   int fail_timer;                  /* The ID of the timer for this gesture. */
} gesture_custom_gesture_t;

/* Allocate and initialize the custom gesture structure */
gesture_custom_gesture_t* custom_gesture_gesture_alloc(custom_gesture_params_t* params,
                                                       gesture_callback_f callback,
                                                       struct gestures_set* set);

/* Initialize the custom parameters */
void custom_gesture_gesture_default_params(custom_gesture_params_t* params);
/*The example below shows the implementation of a custom user-defined gesture. */ 

#include <sys/types.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include "gestures/set.h"
#include "gestures/timer.h"
#include "custom_gesture.h"
#include "input/event_types.h"
#include "gestures/defaults.h"

/* Custom free() function */
void custom_gesture_gesture_free(gesture_base_t* gesture)
{
   free(gesture);
}

/* Custom helper function */
static int _check_valid(gesture_coords_t* coords1, gesture_coords_t* coords2,
                        unsigned max_displacement, unsigned max_ms)
{
   return ((max_displacement_abs(coords1, coords2) <= max_displacement) &&
           (diff_time_ms(coords1, coords2) <= max_ms));
}

/* Custom process_event() function */
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_gesture_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 (custom_gesture->base.state) {
      case GESTURE_STATE_UNRECOGNIZED:
         switch (event->event_type) {
            case INPUT_EVENT_MTOUCH_TOUCH:
               if (set_id > 0) {
                  goto failed;
               } else if (custom_gesture->dt_state > CT_STATE_FIRST_TOUCH) {
                  gesture_timer_clear(gesture, custom_gesture->fail_timer);
                  save_coords(event, &custom_gesture->second_touch);
                  if (!_check_valid(&custom_gesture->first_release,
                                    &custom_gesture->second_touch,
                                    custom_gesture->params.max_displacement,
                                    custom_gesture->params.max_delay_ms)) {
                     goto failed;
                  }
                  custom_gesture->dt_state = CT_STATE_SECOND_TOUCH;
               } else {
                  save_coords(event, &custom_gesture->first_touch);
                  custom_gesture->dt_state = CT_STATE_FIRST_TOUCH;
               }

               goto nochange;
            case INPUT_EVENT_MTOUCH_MOVE:
               save_coords(event, &coords);
               if (custom_gesture->dt_state >= CT_STATE_SECOND_TOUCH) {
                  compare_coords = &custom_gesture->second_touch;
               } else {
                  compare_coords = &custom_gesture->first_touch;
               }

               if (!_check_valid(compare_coords, &coords,
                                 custom_gesture->params.max_displacement,
                                 custom_gesture->params.max_hold_ms)) {
                  goto failed;
               }

               goto nochange;
            case INPUT_EVENT_MTOUCH_RELEASE:
               if (custom_gesture->dt_state >= CT_STATE_SECOND_TOUCH) {
                  save_coords(event, &custom_gesture->second_release);
                  if (!_check_valid(&custom_gesture->second_touch,
                                    &custom_gesture->second_release,
                                    custom_gesture->params.max_displacement,
                                    custom_gesture->params.max_hold_ms)) {
                     goto failed;
                  }
                  custom_gesture->dt_state = CT_STATE_SECOND_RELEASE;
                  goto complete;
               } else {
                  save_coords(event, &custom_gesture->first_release);
                  if (!_check_valid(&custom_gesture->first_touch,
                                    &custom_gesture->first_release,
                                    custom_gesture->params.max_displacement,
                                    custom_gesture->params.max_hold_ms)) {
                     goto failed;
                  }
                  custom_gesture->dt_state = CT_STATE_FIRST_RELEASE;

                  /* Set a timer in case an event doesn't come in */
                  gesture_timer_set_event(gesture,
                                          custom_gesture->fail_timer,
                                          custom_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 custom_gesture->base.state;

failed:
   *consumed = 0;
   return GESTURE_STATE_FAILED;

complete:
   *consumed = 1;
   return GESTURE_STATE_COMPLETE;
}

/* Custom timer callback */
static gesture_state_e
timeout(gesture_base_t* base, void* param)
{
   return GESTURE_STATE_FAILED;
}

/* Custom reset() function */
void custom_gesture_gesture_reset(gesture_base_t* gesture)
{
   gesture_custom_gesture_t* custom_gesture = (gesture_custom_gesture_t*)gesture;
   custom_gesture->dt_state = CT_STATE_INIT;
}

/* Initialize custom gesture parameters with default values. */
void custom_gesture_gesture_default_params(custom_gesture_params_t* params)
{
   params->max_displacement = GESTURE_MAX_MOVE_TOLERANCE_PIX;
   params->max_hold_ms = GESTURE_MAX_TAP_DELAY_MS;
   params->max_delay_ms = GESTURE_MIN_HOLD_DELAY_MS;
}

/* Custom alloc() function */
gesture_custom_gesture_t* custom_gesture_gesture_alloc(custom_gesture_params_t* params,
                                                       gesture_callback_f callback,
                                                       struct gestures_set* set)
{
	gesture_custom_gesture_t* custom_gesture = calloc(1, sizeof(*custom_gesture));
   if (NULL == custom_gesture) {
      return NULL;
   }

   gesture_base_init(&custom_gesture->base);
   gestures_set_add(set, &custom_gesture->base);
   custom_gesture->base.type = GESTURE_USER;
   custom_gesture->base.funcs.free = custom_gesture_gesture_free;
   custom_gesture->base.funcs.process_event = custom_gesture_gesture_process_event;
   custom_gesture->base.funcs.reset = custom_gesture_gesture_reset;
   custom_gesture->base.callback = callback;
   custom_gesture->fail_timer = gesture_timer_create(&custom_gesture->base, timeout, NULL);
   custom_gesture_gesture_reset(&custom_gesture->base);

   if (NULL != params) {
      custom_gesture->params = *params;
   } else {
      custom_gesture_gesture_default_params(&custom_gesture->params);
   }

   return custom_gesture;
}

Last modified: 2014-06-24



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus