efl/legacy/elementary/src/lib/elm_gesture_layer.c

3553 lines
109 KiB
C
Raw Normal View History

#include <Elementary.h>
#include "elm_priv.h"
/** @defgroup Elm_Gesture_Layer Gesture Layer */
/* Some defaults */
#define ELM_MOUSE_DEVICE 0
/* ELM_GESTURE_NEGATIVE_ANGLE - magic number says we didn't compute this yet */
#define ELM_GESTURE_NEGATIVE_ANGLE (-1.0) /* Magic number */
#define ELM_GESTURE_MOMENTUM_DELAY 25
#define ELM_GESTURE_MOMENTUM_TIMEOUT 50
#define ELM_GESTURE_MULTI_TIMEOUT 50
#define ELM_GESTURE_MINIMUM_MOMENTUM 0.001
/* Some Trigo values */
#define RAD_90DEG M_PI_2
#define RAD_180DEG M_PI
#define RAD_270DEG (M_PI_2 * 3)
#define RAD_360DEG (M_PI * 2)
/* #define DEBUG_GESTURE_LAYER 1 */
#define RAD2DEG(x) ((x) * 57.295779513)
#define DEG2RAD(x) ((x) / 57.295779513)
static void *
_glayer_bufdup(void *buf, size_t size)
{
void *p;
p = malloc(size);
memcpy(p, buf, size);
return p;
}
#define COPY_EVENT_INFO(EV) _glayer_bufdup(EV, sizeof(*EV))
#define SET_TEST_BIT(P) do { \
P->test = P->fn[ELM_GESTURE_STATE_START].cb || P->fn[ELM_GESTURE_STATE_MOVE].cb || P->fn[ELM_GESTURE_STATE_END].cb || P->fn[ELM_GESTURE_STATE_ABORT].cb; \
} while (0)
#define IS_TESTED(T) ((wd->gesture[T]) ? wd->gesture[T]->test : EINA_FALSE)
/**
* @internal
*
* @struct _Func_Data
* Struct holds callback information.
*
* @ingroup Elm_Gesture_Layer
*/
struct _Func_Data
{
void *user_data; /**< Holds user data to CB (like sd) */
Elm_Gesture_Event_Cb cb;
};
/**
* @internal
*
* @typedef Func_Data
* type for callback information
*
* @ingroup Elm_Gesture_Layer
*/
typedef struct _Func_Data Func_Data;
/**
* @internal
*
* @struct _Gesture_Info
* Struct holds gesture info
*
* @ingroup Elm_Gesture_Layer
*/
struct _Gesture_Info
{
Evas_Object *obj;
void *data; /**< Holds gesture intemidiate processing data */
Func_Data fn[ELM_GESTURE_STATE_ABORT + 1]; /**< Callback info for states */
Elm_Gesture_Type g_type; /**< gesture type */
Elm_Gesture_State state; /**< gesture state */
void *info; /**< Data for the state callback */
Eina_Bool test; /**< if true this gesture should be tested on input */
};
/**
* @internal
*
* @typedef Gesture_Info
* Type for _Gesture_Info
*
* @ingroup Elm_Gesture_Layer
*/
typedef struct _Gesture_Info Gesture_Info;
/**
* @internal
*
* @struct _Event_History
* Struct holds event history.
* These events are repeated if no gesture found.
*
* @ingroup Elm_Gesture_Layer
*/
struct _Event_History
{
EINA_INLIST;
void *event;
Evas_Callback_Type event_type;
};
/**
* @internal
*
* @typedef Event_History
* Type for _Event_History
*
* @ingroup Elm_Gesture_Layer
*/
typedef struct _Event_History Event_History;
/**
* @internal
*
* @struct _Pointer_Event
* Struct holds pointer-event info
* This is a generic pointer event structure
*
* @ingroup Elm_Gesture_Layer
*/
struct _Pointer_Event
{
Evas_Coord x, y;
unsigned int timestamp;
int device;
Evas_Callback_Type event_type;
};
/**
* @internal
*
* @typedef Pointer_Event
* Type for generic pointer event structure
*
* @ingroup Elm_Gesture_Layer
*/
typedef struct _Pointer_Event Pointer_Event;
/* All *Type structs hold result for the user in 'info' field
* The rest is gesture processing intermediate data.
* NOTE: info field must be FIRST in the struct.
* This is used when reporting ABORT in event_history_clear() */
struct _Taps_Type
{
Elm_Gesture_Taps_Info info;
unsigned int sum_x;
unsigned int sum_y;
unsigned int n_taps_needed;
unsigned int n_taps;
Eina_List *l;
};
typedef struct _Taps_Type Taps_Type;
struct _Long_Tap_Type
{
Elm_Gesture_Taps_Info info;
Evas_Coord center_x;
Evas_Coord center_y;
unsigned int max_touched;
Ecore_Timer *timeout; /* When this expires, long tap STARTed */
Eina_List *touched;
};
typedef struct _Long_Tap_Type Long_Tap_Type;
struct _Momentum_Type
{ /* Fields used by _line_test() */
Elm_Gesture_Momentum_Info info;
Evas_Coord_Point line_st;
Evas_Coord_Point line_end;
unsigned int t_st_x; /* Time start on X */
unsigned int t_st_y; /* Time start on Y */
unsigned int t_end; /* Time end */
unsigned int t_up; /* Recent up event time */
int xdir, ydir;
};
typedef struct _Momentum_Type Momentum_Type;
struct _Line_Data
{
Evas_Coord_Point line_st;
Evas_Coord_Point line_end;
Evas_Coord line_length;
unsigned int t_st; /* Time start */
unsigned int t_end; /* Time end */
int device;
double line_angle; /* Current angle of line */
};
typedef struct _Line_Data Line_Data;
struct _Line_Type
{ /* Fields used by _line_test() */
Elm_Gesture_Line_Info info;
Eina_List *list; /* List of Line_Data */
};
typedef struct _Line_Type Line_Type;
struct _Zoom_Type
{ /* Fields used by _zoom_test() */
Elm_Gesture_Zoom_Info info;
Pointer_Event zoom_st;
Pointer_Event zoom_mv;
Pointer_Event zoom_st1;
Pointer_Event zoom_mv1;
Evas_Event_Mouse_Wheel *zoom_wheel;
Evas_Coord zoom_base; /* Holds gap between fingers on zoom-start */
Evas_Coord zoom_distance_tolerance;
unsigned int m_st_tm; /* momentum start time */
unsigned int m_prev_tm; /* momentum prev time */
int dir; /* Direction: 1=zoom-in, (-1)=zoom-out */
double m_base; /* zoom value when momentum starts */
double next_step;
};
typedef struct _Zoom_Type Zoom_Type;
struct _Rotate_Type
{ /* Fields used by _rotation_test() */
Elm_Gesture_Rotate_Info info;
Pointer_Event rotate_st;
Pointer_Event rotate_mv;
Pointer_Event rotate_st1;
Pointer_Event rotate_mv1;
unsigned int prev_momentum_tm; /* timestamp of prev_momentum */
double prev_momentum; /* Snapshot of momentum 0.01 sec ago */
double accum_momentum;
double rotate_angular_tolerance;
double next_step;
};
typedef struct _Rotate_Type Rotate_Type;
struct _Widget_Data
{
Evas_Object *target; /* Target Widget */
Event_History *event_history_list;
int line_min_length;
Evas_Coord zoom_distance_tolerance;
Evas_Coord line_distance_tolerance;
double line_angular_tolerance;
double zoom_wheel_factor; /* mouse wheel zoom steps */
double zoom_finger_factor; /* used for zoom factor */
double rotate_angular_tolerance;
unsigned int flick_time_limit_ms;
double long_tap_start_timeout;
Eina_Bool glayer_continues_enable;
double zoom_step;
double rotate_step;
Gesture_Info *gesture[ELM_GESTURE_LAST];
Ecore_Timer *dbl_timeout; /* When this expires, dbl click/taps ABORTed */
Eina_List *pending; /* List of devices need to refeed *UP event */
Eina_List *touched; /* Information of touched devices */
Eina_Bool repeat_events : 1;
};
typedef struct _Widget_Data Widget_Data;
static const char *widtype = NULL;
static void _del_hook(Evas_Object *obj);
static Eina_Bool _event_history_clear(Evas_Object *obj);
static void _reset_states(Widget_Data *wd);
static void _key_down_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info);
static void _key_up_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info);
static void _zoom_with_wheel_test(Evas_Object *obj, void *event_info, Evas_Callback_Type event_type, Elm_Gesture_Type g_type);
static void _mouse_wheel(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info);
static void _mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _multi_down(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info);
static void _multi_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info);
static void _multi_up(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info);
/* START - Functions to manage touched-device list */
/**
* @internal
* This function is used to find if device is touched
*
* @ingroup Elm_Gesture_Layer
*/
static int
compare_device(const void *data1, const void *data2)
{ /* Compare the two device numbers */
return (((Pointer_Event *) data1)->device -((Pointer_Event *) data2)->device);
}
/**
* @internal
*
* Remove Pointer Event from touched device list
* @param list Pointer to touched device list.
* @param Pointer_Event Pointer to PE.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_List *
_remove_touched_device(Eina_List *list, Pointer_Event *pe)
{
Eina_List *lst = NULL;
Pointer_Event *p = eina_list_search_unsorted(list, compare_device, pe);
if (p)
{
lst = eina_list_remove(list, p);
free(p);
return lst;
}
return list;
}
/**
* @internal
*
* Recoed Pointer Event in touched device list
* Note: This fuction allocates memory for PE event
* This memory is released in _remove_touched_device()
* @param list Pointer to touched device list.
* @param Pointer_Event Pointer to PE.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_List *
_add_touched_device(Eina_List *list, Pointer_Event *pe)
{
Pointer_Event *p = eina_list_search_unsorted(list, compare_device, pe);
if (p)
{ /* We like to track device touch-position, overwrite info */
memcpy(p, pe, sizeof(Pointer_Event));
return list;
}
if ((pe->event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
(pe->event_type == EVAS_CALLBACK_MULTI_DOWN))
{ /* Add touched device on DOWN event only */
p = malloc(sizeof(Pointer_Event));
/* Freed in _remove_touched_device() */
memcpy(p, pe, sizeof(Pointer_Event));
return eina_list_append(list, p);
}
return list;
}
/* END - Functions to manage touched-device list */
/**
* @internal
*
* Get event flag
* @param event_info pointer to event.
*
* @ingroup Elm_Gesture_Layer
*/
static Evas_Event_Flags
_get_event_flag(void *event_info, Evas_Callback_Type event_type)
{
switch(event_type)
{
case EVAS_CALLBACK_MOUSE_IN:
return ((Evas_Event_Mouse_In *) event_info)->event_flags;
case EVAS_CALLBACK_MOUSE_OUT:
return ((Evas_Event_Mouse_Out *) event_info)->event_flags;
case EVAS_CALLBACK_MOUSE_DOWN:
return ((Evas_Event_Mouse_Down *) event_info)->event_flags;
case EVAS_CALLBACK_MOUSE_MOVE:
return ((Evas_Event_Mouse_Move *) event_info)->event_flags;
case EVAS_CALLBACK_MOUSE_UP:
return ((Evas_Event_Mouse_Up *) event_info)->event_flags;
case EVAS_CALLBACK_MOUSE_WHEEL:
return ((Evas_Event_Mouse_Wheel *) event_info)->event_flags;
case EVAS_CALLBACK_MULTI_DOWN:
return ((Evas_Event_Multi_Down *) event_info)->event_flags;
case EVAS_CALLBACK_MULTI_MOVE:
return ((Evas_Event_Multi_Move *) event_info)->event_flags;
case EVAS_CALLBACK_MULTI_UP:
return ((Evas_Event_Multi_Up *) event_info)->event_flags;
case EVAS_CALLBACK_KEY_DOWN:
return ((Evas_Event_Key_Down *) event_info)->event_flags;
case EVAS_CALLBACK_KEY_UP:
return ((Evas_Event_Key_Up *) event_info)->event_flags;
default:
return EVAS_EVENT_FLAG_NONE;
}
}
/**
* @internal
*
* Sets event flag to value returned from user callback
* @param wd Widget Data
* @param event_info pointer to event.
* @param event_type what type was ev (mouse down, etc...)
* @param ev_flags event flags
*
* @ingroup Elm_Gesture_Layer
*/
static void
consume_event(Widget_Data *wd, void *event_info,
Evas_Callback_Type event_type, Evas_Event_Flags ev_flags)
{ /* Mark EVAS_EVENT_FLAG_ON_HOLD on events that are used by gesture layer */
/* ev_flags != EVAS_EVENT_FLAG_NONE means target used event and g-layer */
/* should not refeed this event. */
if (!event_info)
return; /* This happens when restarting gestures */
if (!wd->repeat_events) ev_flags |= EVAS_EVENT_FLAG_ON_HOLD;
if (ev_flags)
{
switch(event_type)
{
case EVAS_CALLBACK_MOUSE_DOWN:
((Evas_Event_Mouse_Down *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_MOUSE_MOVE:
((Evas_Event_Mouse_Move *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_MOUSE_UP:
((Evas_Event_Mouse_Up *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_MOUSE_WHEEL:
((Evas_Event_Mouse_Wheel *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_MULTI_DOWN:
((Evas_Event_Multi_Down *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_MULTI_MOVE:
((Evas_Event_Multi_Move *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_MULTI_UP:
((Evas_Event_Multi_Up *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_KEY_DOWN:
((Evas_Event_Key_Down *) event_info)->event_flags |= ev_flags;
break;
case EVAS_CALLBACK_KEY_UP:
((Evas_Event_Key_Up *) event_info)->event_flags |= ev_flags;
break;
default:
return;
}
}
}
/**
* @internal
*
* Report current state of a gesture by calling user callback.
* @param gesture what gesture state we report.
* @param info inforamtion for user callback
*
* @ingroup Elm_Gesture_Layer
*/
static Evas_Event_Flags
_report_state(Gesture_Info *gesture, void *info)
{ /* We report current state (START, MOVE, END, ABORT), once */
#if defined(DEBUG_GESTURE_LAYER)
printf("%s reporting gesture=<%d> state=<%d>\n" , __func__, gesture->g_type,
gesture->state);
#endif
if ((gesture->state != ELM_GESTURE_STATE_UNDEFINED) &&
(gesture->fn[gesture->state].cb))
{ /* Fill state-info struct and send ptr to user callback */
return gesture->fn[gesture->state].cb(
gesture->fn[gesture->state].user_data, info);
}
return EVAS_EVENT_FLAG_NONE;
}
/**
* @internal
*
* Update state for a given gesture.
* We may update gesture state to:
* UNDEFINED - current input did not start gesure yet.
* START - gesture started according to input.
* MOVE - gusture in progress.
* END - gesture completed according to input.
* ABORT - input does not matches gesure.
* note that we may move from UNDEFINED to ABORT
* because we may detect that gesture will not START
* with a given input.
*
* @param g given gesture to change state.
* @param s gesure new state.
* @param info buffer to be sent to user callback on report_state.
* @param force makes report_state to report the new-state even
* if its same as current state. Works for MOVE - gesture in progress.
*
* @ingroup Elm_Gesture_Layer
*/
static Evas_Event_Flags
_set_state(Gesture_Info *g, Elm_Gesture_State s,
void *info, Eina_Bool force)
{
Elm_Gesture_State old_state;
if ((g->state == s) && (!force))
return EVAS_EVENT_FLAG_NONE;
old_state = g->state;
g->state = s;
g->info = info; /* Information for user callback */
if ((g->state == ELM_GESTURE_STATE_ABORT) ||
(g->state == ELM_GESTURE_STATE_END))
g->test = EINA_FALSE;
if ((g->state != ELM_GESTURE_STATE_UNDEFINED) &&
(!((old_state == ELM_GESTURE_STATE_UNDEFINED) &&
(s == ELM_GESTURE_STATE_ABORT))))
return _report_state(g, g->info);
return EVAS_EVENT_FLAG_NONE;
}
/**
* @internal
*
* This resets all gesture states and sets test-bit.
* this is used for restarting gestures to listen to input.
* happens after we complete a gesture or no gesture was detected.
* @param wd Widget data of the gesture-layer object.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_reset_states(Widget_Data *wd)
{
int i;
Gesture_Info *p;
for (i = ELM_GESTURE_FIRST; i < ELM_GESTURE_LAST; i++)
{
p = wd->gesture[i];
if (p)
{
_set_state(p, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
SET_TEST_BIT(p);
}
}
}
/**
* @internal
*
* if gesture was NOT detected AND we only have gestures in ABORT state
* we clear history immediately to be ready for input.
*
* @param obj The gesture-layer object.
* @return TRUE on event history_clear
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_clear_if_finished(Evas_Object *obj)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return EINA_FALSE;
int i;
/* Clear history if all we have aborted gestures */
Eina_Bool reset_s = EINA_TRUE, all_undefined = EINA_TRUE;
for (i = ELM_GESTURE_FIRST; i < ELM_GESTURE_LAST; i++)
{ /* If no gesture started and all we have aborted gestures, reset all */
Gesture_Info *p = wd->gesture[i];
if ((p) && (p->state != ELM_GESTURE_STATE_UNDEFINED))
{
if ((p->state == ELM_GESTURE_STATE_START) ||
(p->state == ELM_GESTURE_STATE_MOVE))
reset_s = EINA_FALSE;
all_undefined = EINA_FALSE;
}
}
if (reset_s && (!all_undefined))
return _event_history_clear(obj);
return EINA_FALSE;
}
static Eina_Bool
2012-03-07 00:05:42 -08:00
_inside(Evas_Coord xx1, Evas_Coord yy1, Evas_Coord xx2, Evas_Coord yy2)
{
int w = _elm_config->finger_size >> 1; /* Finger size devided by 2 */
2012-03-07 00:05:42 -08:00
if (xx1 < (xx2 - w))
return EINA_FALSE;
2012-03-07 00:05:42 -08:00
if (xx1 > (xx2 + w))
return EINA_FALSE;
2012-03-07 00:05:42 -08:00
if (yy1 < (yy2 - w))
return EINA_FALSE;
2012-03-07 00:05:42 -08:00
if (yy1 > (yy2 + w))
return EINA_FALSE;
return EINA_TRUE;
}
/* All *test_reset() funcs are called to clear
* gesture intermediate data.
* This happens when we need to reset our tests.
* for example when gesture is detected or all ABORTed. */
static void
_tap_gestures_test_reset(Gesture_Info *gesture)
{
if (!gesture)
return;
Widget_Data *wd = elm_widget_data_get(gesture->obj);
Eina_List *data;
Pointer_Event *pe;
if (wd->dbl_timeout)
{
ecore_timer_del(wd->dbl_timeout);
wd->dbl_timeout = NULL;
}
if (!gesture->data)
return;
EINA_LIST_FREE(((Taps_Type *) gesture->data)->l, data)
EINA_LIST_FREE(data, pe)
free(pe);
memset(gesture->data, 0, sizeof(Taps_Type));
}
/* All *test_reset() funcs are called to clear
* gesture intermediate data.
* This happens when we need to reset our tests.
* for example when gesture is detected or all ABORTed. */
static void
_n_long_tap_test_reset(Gesture_Info *gesture)
{
if (!gesture)
return;
if (!gesture->data)
return;
Long_Tap_Type *st = gesture->data;
Eina_List *l;
Pointer_Event *p;
EINA_LIST_FOREACH(st->touched, l, p)
free(p);
eina_list_free(st->touched);
if (st->timeout)
{
ecore_timer_del(st->timeout);
st->timeout = NULL;
}
memset(gesture->data, 0, sizeof(Long_Tap_Type));
}
static void
_momentum_test_reset(Gesture_Info *gesture)
{
if (!gesture)
return;
if (!gesture->data)
return;
memset(gesture->data, 0, sizeof(Momentum_Type));
}
static void
_line_data_reset(Line_Data *st)
{
if (!st)
return;
memset(st, 0, sizeof(Line_Data));
st->line_angle = ELM_GESTURE_NEGATIVE_ANGLE;
}
static void
_line_test_reset(Gesture_Info *gesture)
{
if (!gesture)
return;
if (!gesture->data)
return;
Line_Type *st = gesture->data;
Eina_List *list = st->list;
Eina_List *l;
Line_Data *t_line;
EINA_LIST_FOREACH(list, l, t_line)
free(t_line);
eina_list_free(list);
st->list = NULL;
}
static void
_zoom_test_reset(Gesture_Info *gesture)
{
if (!gesture)
return;
if (!gesture->data)
return;
Widget_Data *wd = elm_widget_data_get(gesture->obj);
Zoom_Type *st = gesture->data;
Evas_Modifier_Mask mask = evas_key_modifier_mask_get(
evas_object_evas_get(wd->target), "Control");
evas_object_key_ungrab(wd->target, "Control_L", mask, 0);
evas_object_key_ungrab(wd->target, "Control_R", mask, 0);
memset(st, 0, sizeof(Zoom_Type));
st->zoom_distance_tolerance = wd->zoom_distance_tolerance;
st->info.zoom = 1.0;
}
static void
_rotate_test_reset(Gesture_Info *gesture)
{
if (!gesture)
return;
if (!gesture->data)
return;
Widget_Data *wd = elm_widget_data_get(gesture->obj);
Rotate_Type *st = gesture->data;
memset(st, 0, sizeof(Rotate_Type));
st->info.base_angle = ELM_GESTURE_NEGATIVE_ANGLE;
st->rotate_angular_tolerance = wd->rotate_angular_tolerance;
}
/**
* @internal
*
* We register callbacks when gesture layer is attached to an object
* or when its enabled after disable.
*
* @param obj The gesture-layer object.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_register_callbacks(Evas_Object *obj)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (wd->target)
{
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MOUSE_DOWN,
_mouse_down, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MOUSE_MOVE,
_mouse_move, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MOUSE_UP,
_mouse_up, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MOUSE_WHEEL,
_mouse_wheel, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MULTI_DOWN,
_multi_down, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MULTI_MOVE,
_multi_move, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_MULTI_UP,
_multi_up, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_KEY_DOWN,
_key_down_cb, obj);
evas_object_event_callback_add(wd->target, EVAS_CALLBACK_KEY_UP,
_key_up_cb, obj);
}
}
/**
* @internal
*
* We unregister callbacks when gesture layer is disabled.
*
* @param obj The gesture-layer object.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_unregister_callbacks(Evas_Object *obj)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (wd->target)
{
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MOUSE_DOWN,
_mouse_down);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MOUSE_MOVE,
_mouse_move);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MOUSE_UP,
_mouse_up);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MOUSE_WHEEL,
_mouse_wheel);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MULTI_DOWN,
_multi_down);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MULTI_MOVE,
_multi_move);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_MULTI_UP,
_multi_up);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_KEY_DOWN,
_key_down_cb);
evas_object_event_callback_del(wd->target, EVAS_CALLBACK_KEY_UP,
_key_up_cb);
}
}
/* START - Event history list handling functions */
/**
* @internal
* This function is used to find if device number
* is found in a list of devices.
* The list contains devices for refeeding *UP event
*
* @ingroup Elm_Gesture_Layer
*/
static int
device_in_pending_list(const void *data1, const void *data2)
{ /* Compare the two device numbers */
return (((intptr_t) data1) - ((intptr_t) data2));
}
/**
* @internal
*
* This functions adds device to refeed-pending device list
* @ingroup Elm_Gesture_Layer
*/
static Eina_List *
_add_device_pending(Eina_List *list, void *event, Evas_Callback_Type event_type)
{
int device = ELM_MOUSE_DEVICE;
switch(event_type)
{
case EVAS_CALLBACK_MOUSE_DOWN:
break;
case EVAS_CALLBACK_MULTI_DOWN:
device = ((Evas_Event_Multi_Down *) event)->device;
break;
default:
return list;
}
if (!eina_list_search_unsorted_list(list, device_in_pending_list,
(void *)(intptr_t)device))
{
return eina_list_append(list, (void *)(intptr_t)device);
}
return list;
}
/**
* @internal
*
* This functions returns pending-device node
* @ingroup Elm_Gesture_Layer
*/
static Eina_List *
_device_is_pending(Eina_List *list, void *event, Evas_Callback_Type event_type)
{
int device = ELM_MOUSE_DEVICE;
switch(event_type)
{
case EVAS_CALLBACK_MOUSE_UP:
break;
case EVAS_CALLBACK_MULTI_UP:
device = ((Evas_Event_Multi_Up *) event)->device;
break;
default:
return NULL;
}
return eina_list_search_unsorted_list(list, device_in_pending_list,
(void *)(intptr_t)device);
}
/**
* @internal
*
* This function reports ABORT to all none-detected gestures
* Then resets test bits for all desired gesures
* and clears input-events history.
* note: if no gesture was detected, events from history list
* are streamed to the widget because it's unused by layer.
* user may cancel refeed of events by setting repeat events.
*
* @param obj The gesture-layer object.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_event_history_clear(Evas_Object *obj)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return EINA_FALSE;
int i;
Gesture_Info *p;
Evas *e = evas_object_evas_get(obj);
Eina_Bool gesture_found = EINA_FALSE;
for (i = ELM_GESTURE_FIRST; i < ELM_GESTURE_LAST; i++)
{
p = wd->gesture[i];
if (p)
{
if (p->state == ELM_GESTURE_STATE_END)
gesture_found = EINA_TRUE;
else
{ /* Report ABORT to all gestures that still not finished */
_set_state(p, ELM_GESTURE_STATE_ABORT, wd->gesture[i]->info,
EINA_FALSE);
}
}
}
_reset_states(wd); /* we are ready to start testing for gestures again */
/* Clear all gestures intermediate data */
if (IS_TESTED(ELM_GESTURE_N_LONG_TAPS))
{ /* We do not clear a long-tap gesture if fingers still on surface */
/* and gesture timer still pending to test gesture state */
Long_Tap_Type *st = wd->gesture[ELM_GESTURE_N_LONG_TAPS]->data;
if ((st) && /* st not allocated if clear occurs before 1st input */
((!eina_list_count(st->touched)) || (!st->timeout)))
_n_long_tap_test_reset(wd->gesture[ELM_GESTURE_N_LONG_TAPS]);
}
if (wd->dbl_timeout)
{
ecore_timer_del(wd->dbl_timeout);
wd->dbl_timeout = NULL;
}
_tap_gestures_test_reset(wd->gesture[ELM_GESTURE_N_TAPS]);
_tap_gestures_test_reset(wd->gesture[ELM_GESTURE_N_DOUBLE_TAPS]);
_tap_gestures_test_reset(wd->gesture[ELM_GESTURE_N_TRIPLE_TAPS]);
_momentum_test_reset(wd->gesture[ELM_GESTURE_MOMENTUM]);
_line_test_reset(wd->gesture[ELM_GESTURE_N_LINES]);
_line_test_reset(wd->gesture[ELM_GESTURE_N_FLICKS]);
_zoom_test_reset(wd->gesture[ELM_GESTURE_ZOOM]);
_rotate_test_reset(wd->gesture[ELM_GESTURE_ROTATE]);
/* Disable gesture layer so refeeded events won't be consumed by it */
_unregister_callbacks(obj);
while (wd->event_history_list)
{
Event_History *t;
t = wd->event_history_list;
Eina_List *pending = _device_is_pending(wd->pending,
wd->event_history_list->event,
wd->event_history_list->event_type);
/* Refeed events if no gesture matched input */
if (pending || ((!gesture_found) && (!wd->repeat_events)))
{
evas_event_refeed_event(e, wd->event_history_list->event,
wd->event_history_list->event_type);
if (pending)
{
wd->pending = eina_list_remove_list(wd->pending, pending);
}
else
{
wd->pending = _add_device_pending(wd->pending,
wd->event_history_list->event,
wd->event_history_list->event_type);
}
}
free(wd->event_history_list->event);
wd->event_history_list = (Event_History *) eina_inlist_remove(
EINA_INLIST_GET(wd->event_history_list),
EINA_INLIST_GET(wd->event_history_list));
free(t);
}
_register_callbacks(obj);
return EINA_TRUE;
}
/**
* @internal
*
* This function copies input events.
* We copy event info before adding it to history.
* The memory is freed when we clear history.
*
* @param event the event to copy
* @param event_type event type to copy
*
* @ingroup Elm_Gesture_Layer
*/
static void *
_copy_event_info(void *event, Evas_Callback_Type event_type)
{
switch(event_type)
{
case EVAS_CALLBACK_MOUSE_DOWN:
return COPY_EVENT_INFO((Evas_Event_Mouse_Down *) event);
break;
case EVAS_CALLBACK_MOUSE_MOVE:
return COPY_EVENT_INFO((Evas_Event_Mouse_Move *) event);
break;
case EVAS_CALLBACK_MOUSE_UP:
return COPY_EVENT_INFO((Evas_Event_Mouse_Up *) event);
break;
case EVAS_CALLBACK_MOUSE_WHEEL:
return COPY_EVENT_INFO((Evas_Event_Mouse_Wheel *) event);
break;
case EVAS_CALLBACK_MULTI_DOWN:
return COPY_EVENT_INFO((Evas_Event_Multi_Down *) event);
break;
case EVAS_CALLBACK_MULTI_MOVE:
return COPY_EVENT_INFO((Evas_Event_Multi_Move *) event);
break;
case EVAS_CALLBACK_MULTI_UP:
return COPY_EVENT_INFO((Evas_Event_Multi_Up *) event);
break;
case EVAS_CALLBACK_KEY_DOWN:
return COPY_EVENT_INFO((Evas_Event_Key_Down *) event);
break;
case EVAS_CALLBACK_KEY_UP:
return COPY_EVENT_INFO((Evas_Event_Key_Up *) event);
break;
default:
return NULL;
}
}
static Eina_Bool
_event_history_add(Evas_Object *obj, void *event, Evas_Callback_Type event_type)
{
Widget_Data *wd = elm_widget_data_get(obj);
Event_History *ev;
if (!wd) return EINA_FALSE;
ev = malloc(sizeof(Event_History));
ev->event = _copy_event_info(event, event_type); /* Freed on event_history_clear */
ev->event_type = event_type;
wd->event_history_list = (Event_History *) eina_inlist_append(
EINA_INLIST_GET(wd->event_history_list), EINA_INLIST_GET(ev));
return EINA_TRUE;
}
/* END - Event history list handling functions */
static void
_del_hook(Evas_Object *obj)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
_event_history_clear(obj);
eina_list_free(wd->pending);
Pointer_Event *data;
EINA_LIST_FREE(wd->touched, data)
free(data);
if (!elm_widget_disabled_get(obj))
_unregister_callbacks(obj);
/* Free all gestures internal data structures */
int i;
for (i = 0; i < ELM_GESTURE_LAST; i++)
if (wd->gesture[i])
{
if (wd->gesture[i]->data)
free(wd->gesture[i]->data);
free(wd->gesture[i]);
}
free(wd);
}
static int
compare_match_fingers(const void *data1, const void *data2)
{ /* Compare coords of first item in list to cur coords */
const Pointer_Event *pe1 = eina_list_data_get(data1);
const Pointer_Event *pe2 = data2;
if (_inside(pe1->x, pe1->y, pe2->x, pe2->y))
return 0;
else if (pe1->x < pe2->x)
return -1;
else
{
if (pe1->x == pe2->x)
return pe1->y - pe2->y;
else
return 1;
}
}
static int
compare_pe_device(const void *data1, const void *data2)
{ /* Compare device of first item in list to our pe device */
const Pointer_Event *pe1 = eina_list_data_get(data1);
const Pointer_Event *pe2 = data2;
/* Only match if last was a down event */
if ((pe1->event_type != EVAS_CALLBACK_MULTI_DOWN) &&
(pe1->event_type != EVAS_CALLBACK_MOUSE_DOWN))
return 1;
if (pe1->device == pe2->device)
return 0;
else if (pe1->device < pe2->device)
return -1;
else
return 1;
}
static Eina_List*
_record_pointer_event(Taps_Type *st, Eina_List *pe_list, Pointer_Event *pe,
Widget_Data *wd, void *event_info, Evas_Callback_Type event_type)
{ /* Keep copy of pe and record it in list */
Pointer_Event *p = malloc(sizeof(Pointer_Event));
memcpy(p, pe, sizeof(Pointer_Event));
consume_event(wd, event_info, event_type, EVAS_EVENT_FLAG_NONE);
st->sum_x += pe->x;
st->sum_y += pe->y;
st->n_taps++;
/* This will also update middle-point to report to user later */
st->info.x = st->sum_x / st->n_taps;
st->info.y = st->sum_y / st->n_taps;
st->info.timestamp = pe->timestamp;
if (!pe_list)
{
pe_list = eina_list_append(pe_list, p);
st->l = eina_list_append(st->l, pe_list);
}
else
pe_list = eina_list_append(pe_list, p);
return pe_list;
}
/**
* @internal
*
* This function checks if the tap gesture is done.
*
* @param data gesture info pointer
* @return EINA_TRUE if it is done.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_tap_gesture_check_finish(Gesture_Info *gesture)
{
/* Here we check if taps-gesture was completed successfuly */
/* Count how many taps were recieved on each device then */
/* determine if it matches n_taps_needed defined on START */
Taps_Type *st = gesture->data;
Eina_List *l;
Eina_List *pe_list;
if (!st || !st->l) return EINA_FALSE;
EINA_LIST_FOREACH(st->l, l, pe_list)
{
if (eina_list_count(pe_list) != st->n_taps_needed)
{ /* No match taps number on device, ABORT */
return EINA_FALSE;
}
}
return EINA_TRUE;
}
/**
* @internal
*
* This function sets state a tap-gesture to END or ABORT
*
* @param data gesture info pointer
*
* @ingroup Elm_Gesture_Layer
*/
static void
_tap_gesture_finish(void *data)
{ /* This function will test each tap gesture when timer expires */
Elm_Gesture_State s = ELM_GESTURE_STATE_END;
Gesture_Info *gesture = data;
Taps_Type *st = gesture->data;
if (!_tap_gesture_check_finish(data))
s = ELM_GESTURE_STATE_ABORT;
st->info.n = eina_list_count(st->l);
_set_state(gesture, s, gesture->info, EINA_FALSE);
_tap_gestures_test_reset(gesture);
}
/**
* @internal
*
* when this timer expires we finish tap gestures.
*
* @param data The gesture-layer object.
* @return cancles callback for this timer.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_multi_tap_timeout(void *data)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return EINA_FALSE;
if (IS_TESTED(ELM_GESTURE_N_TAPS))
_tap_gesture_finish(wd->gesture[ELM_GESTURE_N_TAPS]);
if (IS_TESTED(ELM_GESTURE_N_DOUBLE_TAPS))
_tap_gesture_finish(wd->gesture[ELM_GESTURE_N_DOUBLE_TAPS]);
if (IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS))
_tap_gesture_finish(wd->gesture[ELM_GESTURE_N_TRIPLE_TAPS]);
_clear_if_finished(data);
wd->dbl_timeout = NULL;
return ECORE_CALLBACK_CANCEL;
}
/**
* @internal
*
* when this timer expires we START long tap gesture
*
* @param data The gesture-layer object.
* @return cancles callback for this timer.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_long_tap_timeout(void *data)
{
Gesture_Info *gesture = data;
Long_Tap_Type *st = gesture->data;
st->timeout = NULL;
_set_state(gesture, ELM_GESTURE_STATE_START,
gesture->data, EINA_FALSE);
return ECORE_CALLBACK_CANCEL;
}
/**
* @internal
*
* This function checks if a tap gesture should start
*
* @param wd Gesture Layer Widget Data.
* @param pe The recent input event as stored in pe struct.
* @param event_info Original input event pointer.
* @param event_type Type of original input event.
* @param gesture what gesture is tested
* @param how many taps for this gesture (1, 2 or 3)
*
* @return Flag to determine if we need to set a timer for finish
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_tap_gesture_start(Widget_Data *wd, Pointer_Event *pe,
void *event_info, Evas_Callback_Type event_type,
Gesture_Info *gesture, int taps)
{ /* Here we fill Tap struct */
Taps_Type *st = gesture->data;
if (!st)
{ /* Allocated once on first time */
st = calloc(1, sizeof(Taps_Type));
gesture->data = st;
_tap_gestures_test_reset(gesture);
}
Eina_List *pe_list = NULL;
Pointer_Event *pe_down = NULL;
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
switch (pe->event_type)
{
case EVAS_CALLBACK_MULTI_DOWN:
case EVAS_CALLBACK_MOUSE_DOWN:
/* Check if got tap on same cord was tapped before */
pe_list = eina_list_search_unsorted(st->l, compare_match_fingers, pe);
if ((!pe_list) &&
eina_list_search_unsorted(st->l, compare_pe_device, pe))
{ /* This device was touched in other cord before completion */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return EINA_FALSE;
}
pe_list = _record_pointer_event(st, pe_list, pe, wd, event_info, event_type);
if ((pe->device == 0) && (eina_list_count(pe_list) == 1))
{ /* This is the first mouse down we got */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_START,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
st->n_taps_needed = taps * 2; /* count DOWN and UP */
return EINA_TRUE;
}
break;
case EVAS_CALLBACK_MULTI_UP:
case EVAS_CALLBACK_MOUSE_UP:
pe_list = eina_list_search_unsorted(st->l, compare_pe_device, pe);
if (!pe_list)
return EINA_FALSE;
pe_list = _record_pointer_event(st, pe_list, pe, wd, event_info, event_type);
if (((gesture->g_type == ELM_GESTURE_N_TAPS) &&
!IS_TESTED(ELM_GESTURE_N_DOUBLE_TAPS) &&
!IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS)) ||
((gesture->g_type == ELM_GESTURE_N_DOUBLE_TAPS) &&
!IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS)))
{
if (_tap_gesture_check_finish(gesture))
{
_tap_gesture_finish(gesture);
return EINA_FALSE;
}
}
break;
case EVAS_CALLBACK_MULTI_MOVE:
case EVAS_CALLBACK_MOUSE_MOVE:
/* Get first event in first list, this has to be a Mouse Down event */
/* and verify that user didn't move out of this area before next tap */
pe_list = eina_list_search_unsorted(st->l, compare_pe_device, pe);
if (pe_list)
{
pe_down = eina_list_data_get(pe_list);
if (!_inside(pe_down->x, pe_down->y, pe->x, pe->y))
{
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
}
break;
default:
return EINA_FALSE;
}
return EINA_FALSE;
}
/**
* @internal
*
* This function checks all click/tap and double/triple taps
*
* @param obj The gesture-layer object.
* @param pe The recent input event as stored in pe struct.
* @param event_info Original input event pointer.
* @param event_type Type of original input event.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_tap_gestures_test(Evas_Object *obj, Pointer_Event *pe,
void *event_info, Evas_Callback_Type event_type)
{ /* Here we fill Recent_Taps struct and fire-up click/tap timers */
Eina_Bool need_timer = EINA_FALSE;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (!pe) /* this happens when unhandled event arrived */
return; /* see _make_pointer_event function */
if (IS_TESTED(ELM_GESTURE_N_TAPS))
need_timer |= _tap_gesture_start(wd, pe, event_info, event_type,
wd->gesture[ELM_GESTURE_N_TAPS], 1);
if (IS_TESTED(ELM_GESTURE_N_DOUBLE_TAPS))
need_timer |= _tap_gesture_start(wd, pe, event_info, event_type,
wd->gesture[ELM_GESTURE_N_DOUBLE_TAPS], 2);
if (IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS))
need_timer |= _tap_gesture_start(wd, pe, event_info, event_type,
wd->gesture[ELM_GESTURE_N_TRIPLE_TAPS], 3);
if ((need_timer) && (!wd->dbl_timeout))
{ /* Set a timer to finish these gestures */
wd->dbl_timeout = ecore_timer_add(0.4, _multi_tap_timeout,
obj);
}
}
/**
* @internal
*
* This function computes center-point for long-tap gesture
*
* @param st Long Tap gesture info pointer
* @param pe The recent input event as stored in pe struct.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_compute_taps_center(Long_Tap_Type *st,
Evas_Coord *x_out, Evas_Coord *y_out, Pointer_Event *pe)
{
if (!eina_list_count(st->touched))
return;
Eina_List *l;
Pointer_Event *p;
Evas_Coord x = 0, y = 0;
EINA_LIST_FOREACH(st->touched, l, p)
{ /* Accumulate all then take avarage */
if (p->device == pe->device)
{ /* This will take care of values coming from MOVE event */
x += pe->x;
y += pe->y;
}
else
{
x += p->x;
y += p->y;
}
}
*x_out = x / eina_list_count(st->touched);
*y_out = y / eina_list_count(st->touched);
}
/**
* @internal
*
* This function checks N long-tap gesture.
*
* @param obj The gesture-layer object.
* @param pe The recent input event as stored in pe struct.
* @param event_info Original input event pointer.
* @param event_type Type of original input event.
* @param g_type what Gesture we are testing.
* @param taps How many click/taps we test for.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_n_long_tap_test(Evas_Object *obj, Pointer_Event *pe,
void *event_info, Evas_Callback_Type event_type,
Elm_Gesture_Type g_type)
{ /* Here we fill Recent_Taps struct and fire-up click/tap timers */
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (!pe) /* this happens when unhandled event arrived */
return; /* see _make_pointer_event function */
Gesture_Info *gesture = wd->gesture[g_type];
if (!gesture) return;
Long_Tap_Type *st = gesture->data;
if (!st)
{ /* Allocated once on first time */
st = calloc(1, sizeof(Long_Tap_Type));
gesture->data = st;
_n_long_tap_test_reset(gesture);
}
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
switch (pe->event_type)
{
case EVAS_CALLBACK_MULTI_DOWN:
case EVAS_CALLBACK_MOUSE_DOWN:
st->touched = _add_touched_device(st->touched, pe);
st->info.n = eina_list_count(st->touched);
if (st->info.n > st->max_touched)
st->max_touched = st->info.n;
else
{ /* User removed finger from touch, then put back - ABORT */
if ((gesture->state == ELM_GESTURE_STATE_START) ||
(gesture->state == ELM_GESTURE_STATE_MOVE))
{
ev_flag =_set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
}
if ((pe->device == 0) && (eina_list_count(st->touched) == 1))
{ /* This is the first mouse down we got */
st->info.timestamp = pe->timestamp;
/* To test long tap */
/* When this timer expires, gesture STARTED */
if (!st->timeout)
st->timeout = ecore_timer_add(wd->long_tap_start_timeout,
_long_tap_timeout, gesture);
}
consume_event(wd, event_info, event_type, ev_flag);
_compute_taps_center(st, &st->info.x, &st->info.y, pe);
st->center_x = st->info.x;
st->center_y = st->info.y;
break;
case EVAS_CALLBACK_MULTI_UP:
case EVAS_CALLBACK_MOUSE_UP:
st->touched = _remove_touched_device(st->touched, pe);
_compute_taps_center(st, &st->center_x, &st->center_y, pe);
if (st->info.n &&
((gesture->state == ELM_GESTURE_STATE_START) ||
(gesture->state == ELM_GESTURE_STATE_MOVE)))
{ /* Report END only for gesture that STARTed */
if (eina_list_count(st->touched) == 0)
{ /* Report END only at last release event */
ev_flag =_set_state(gesture, ELM_GESTURE_STATE_END,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
}
else
{ /* Stop test, user lifts finger before long-start */
if (st->timeout) ecore_timer_del(st->timeout);
st->timeout = NULL;
ev_flag =_set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
break;
case EVAS_CALLBACK_MULTI_MOVE:
case EVAS_CALLBACK_MOUSE_MOVE:
if (st->info.n &&
((gesture->state == ELM_GESTURE_STATE_START) ||
(gesture->state == ELM_GESTURE_STATE_MOVE)))
{ /* Report MOVE only if STARTED */
Evas_Coord x = 0;
Evas_Coord y = 0;
Elm_Gesture_State state_to_report = ELM_GESTURE_STATE_MOVE;
_compute_taps_center(st, &x, &y, pe);
/* ABORT if user moved fingers out of tap area */
#if defined(DEBUG_GESTURE_LAYER)
printf("%s x,y=(%d,%d) st->info.x,st->info.y=(%d,%d)\n",__func__,x,y,st->info.x,st->info.y);
#endif
if (!_inside(x, y, st->center_x, st->center_y))
state_to_report = ELM_GESTURE_STATE_ABORT;
/* Report MOVE if gesture started */
ev_flag = _set_state(gesture, state_to_report,
&st->info, EINA_TRUE);
consume_event(wd, event_info, event_type, ev_flag);
}
break;
default:
return;
}
}
/**
* @internal
*
* This function computes momentum for MOMENTUM, LINE and FLICK gestures
* This momentum value will be sent to widget when gesture is completed.
*
* @param momentum pointer to buffer where we record momentum value.
* @param x1 x coord where user started gesture.
* @param y1 y coord where user started gesture.
* @param x2 x coord where user completed gesture.
* @param y2 y coord where user completed gesture.
* @param t1x timestamp for X, when user started gesture.
* @param t1y timestamp for Y, when user started gesture.
* @param t2 timestamp when user completed gesture.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_set_momentum(Elm_Gesture_Momentum_Info *momentum,
2012-03-07 00:13:06 -08:00
Evas_Coord xx1, Evas_Coord yy1,
Evas_Coord xx2, Evas_Coord yy2,
2012-03-07 00:13:06 -08:00
unsigned int t1x, unsigned int t1y, unsigned int t2)
{
Evas_Coord velx = 0, vely = 0, vel;
2012-03-07 00:13:06 -08:00
Evas_Coord dx = xx2 - xx1;
Evas_Coord dy = yy2 - yy1;
int dtx = t2 - t1x;
int dty = t2 - t1y;
if (dtx > 0)
velx = (dx * 1000) / dtx;
if (dty > 0)
vely = (dy * 1000) / dty;
vel = sqrt((velx * velx) + (vely * vely));
if ((_elm_config->thumbscroll_friction > 0.0) &&
(vel > _elm_config->thumbscroll_momentum_threshold))
{ /* report momentum */
momentum->mx = velx;
momentum->my = vely;
}
else
{
momentum->mx = 0;
momentum->my = 0;
}
}
/**
* @internal
*
* This function is used for computing rotation angle (DEG).
*
* @param x1 first finger x location.
* @param y1 first finger y location.
* @param x2 second finger x location.
* @param y2 second finger y location.
*
* @return angle of the line between (x1,y1), (x2,y2) in Deg.
* Angles now are given in DEG, not RAD.
* ZERO angle at 12-oclock, growing clockwise.
*
* @ingroup Elm_Gesture_Layer
*/
static double
2012-03-07 00:19:55 -08:00
get_angle(Evas_Coord xx1, Evas_Coord yy1, Evas_Coord xx2, Evas_Coord yy2)
{
double a, xx, yy, rt = (-1);
2012-03-07 00:19:55 -08:00
xx = fabs(xx2 - xx1);
yy = fabs(yy2 - yy1);
2012-03-07 00:19:55 -08:00
if (((int)xx) && ((int)yy))
{
rt = a = RAD2DEG(atan(yy / xx));
2012-03-07 00:19:55 -08:00
if (xx1 < xx2)
{
2012-03-07 00:19:55 -08:00
if (yy1 < yy2) rt = 360 - a;
else rt = a;
}
else
{
2012-03-07 00:19:55 -08:00
if (yy1 < yy2) rt = 180 + a;
else rt = 180 - a;
}
}
if (rt < 0)
{ /* Do this only if rt is not set */
2012-03-07 00:19:55 -08:00
if (((int)xx))
{ /* Horizontal line */
2012-03-07 00:19:55 -08:00
if (xx2 < xx1) rt = 180;
else rt = 0.0;
}
else
{ /* Vertical line */
2012-03-07 00:19:55 -08:00
if (yy2 < yy1) rt = 90;
else rt = 270;
}
}
/* Now we want to change from:
* 90 0
* original circle 180 0 We want: 270 90
* 270 180
*/
rt = 450 - rt;
2012-03-07 00:19:55 -08:00
if (rt >= 360) rt -= 360;
return rt;
}
/**
* @internal
*
* This function is used for computing the magnitude and direction
* of vector between two points.
*
* @param x1 first finger x location.
* @param y1 first finger y location.
* @param x2 second finger x location.
* @param y2 second finger y location.
* @param l length computed (output)
* @param a angle computed (output)
*
* @ingroup Elm_Gesture_Layer
*/
static void
2012-03-07 00:19:55 -08:00
get_vector(Evas_Coord xx1, Evas_Coord yy1, Evas_Coord xx2, Evas_Coord yy2,
Evas_Coord *l, double *a)
{
Evas_Coord xx, yy;
2012-03-07 00:19:55 -08:00
xx = xx2 - xx1;
yy = yy2 - yy1;
*l = (Evas_Coord) sqrt((xx * xx) + (yy * yy));
*a = get_angle(xx1, yy1, xx2, yy2);
}
static int
2012-03-07 00:19:55 -08:00
_get_direction(Evas_Coord xx1, Evas_Coord xx2)
{
2012-03-07 00:19:55 -08:00
if (xx2 < xx1) return -1;
if (xx2 > xx1) return 1;
return 0;
}
/**
* @internal
*
* This function tests momentum gesture.
* @param obj The gesture-layer object.
* @param pe The recent input event as stored in pe struct.
* @param event_info recent input event.
* @param event_type recent event type.
* @param g_type what Gesture we are testing.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_momentum_test(Evas_Object *obj, Pointer_Event *pe,
2012-03-07 00:19:55 -08:00
void *event_info, Evas_Callback_Type event_type,
Elm_Gesture_Type g_type)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
Gesture_Info *gesture = wd->gesture[g_type];
if (!gesture ) return;
/* When continues enable = TRUE a gesture may START on MOVE event */
/* We don't allow this to happen with the if-statement below. */
/* When continues enable = FALSE a gesture may START on DOWN only */
/* Therefor it would NOT start on MOVE event. */
/* NOTE that touched list is updated AFTER this function returns */
/* so (count == 0) when we get here on first touch on surface. */
if ((wd->glayer_continues_enable) && (!eina_list_count(wd->touched)))
return; /* Got move on mouse-over move */
Momentum_Type *st = gesture->data;
Elm_Gesture_State state_to_report = ELM_GESTURE_STATE_MOVE;
if (!st)
{ /* Allocated once on first time */
st = calloc(1, sizeof(Momentum_Type));
gesture->data = st;
_momentum_test_reset(gesture);
}
if (!pe)
return;
/* First make avarage of all touched devices to determine center point */
Eina_List *l;
Pointer_Event *p;
Pointer_Event pe_local = *pe; /* Copy pe event info to local */
unsigned int cnt = 1; /* We start counter counting current pe event */
EINA_LIST_FOREACH(wd->touched, l, p)
if (p->device != pe_local.device)
{
pe_local.x += p->x;
pe_local.y += p->y;
cnt++;
}
/* Compute avarage to get center point */
pe_local.x /= cnt;
pe_local.y /= cnt;
/* If user added finger - reset gesture */
if ((st->info.n) && (st->info.n < cnt))
state_to_report = ELM_GESTURE_STATE_ABORT;
if (st->info.n < cnt)
st->info.n = cnt;
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
switch (event_type)
{
case EVAS_CALLBACK_MOUSE_DOWN:
case EVAS_CALLBACK_MULTI_DOWN:
case EVAS_CALLBACK_MOUSE_MOVE:
case EVAS_CALLBACK_MULTI_MOVE:
if (!st->t_st_x)
{
if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
(event_type == EVAS_CALLBACK_MULTI_DOWN) ||
(wd->glayer_continues_enable)) /* start also on MOVE */
{ /* We start on MOVE when cont-enabled only */
st->line_st.x = st->line_end.x = pe_local.x;
st->line_st.y = st->line_end.y = pe_local.y;
st->t_st_x = st->t_st_y = st->t_end = pe_local.timestamp;
st->xdir = st->ydir = 0;
st->info.x2 = st->info.x1 = pe_local.x;
st->info.y2 = st->info.y1 = pe_local.y;
st->info.tx = st->info.ty = pe_local.timestamp;
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_START,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
return;
}
if (st->t_up)
{
Eina_Bool force = EINA_TRUE; /* for move state */
if ((st->t_up + ELM_GESTURE_MULTI_TIMEOUT) < pe_local.timestamp)
{ /* ABORT if got DOWN or MOVE event after UP+timeout */
state_to_report = ELM_GESTURE_STATE_ABORT;
force = EINA_FALSE;
}
/* We report state but don't compute momentum now */
ev_flag = _set_state(gesture, state_to_report, &st->info,
force);
consume_event(wd, event_info, event_type, ev_flag);
return; /* Stop computing when user remove finger */
}
if ((pe_local.timestamp - ELM_GESTURE_MOMENTUM_TIMEOUT) > st->t_end)
{ /* Too long of a wait, reset all values */
st->line_st.x = pe_local.x;
st->line_st.y = pe_local.y;
st->t_st_y = st->t_st_x = pe_local.timestamp;
st->info.tx = st->t_st_x;
st->info.ty = st->t_st_y;
st->xdir = st->ydir = 0;
}
else
{
int xdir, ydir;
xdir = _get_direction(st->line_end.x, pe_local.x);
ydir = _get_direction(st->line_end.y, pe_local.y);
if (xdir && (xdir != st->xdir))
{
st->line_st.x = st->line_end.x;
st->info.tx = st->t_st_x = st->t_end;
st->xdir = xdir;
}
if (ydir && (ydir != st->ydir))
{
st->line_st.y = st->line_end.y;
st->info.ty = st->t_st_y = st->t_end;
st->ydir = ydir;
}
}
st->info.x2 = st->line_end.x = pe_local.x;
st->info.y2 = st->line_end.y = pe_local.y;
st->t_end = pe_local.timestamp;
_set_momentum(&st->info, st->line_st.x, st->line_st.y,
pe_local.x, pe_local.y, st->t_st_x, st->t_st_y,
pe_local.timestamp);
ev_flag = _set_state(gesture, state_to_report, &st->info,
EINA_TRUE);
consume_event(wd, event_info, event_type, ev_flag);
break;
case EVAS_CALLBACK_MOUSE_UP:
case EVAS_CALLBACK_MULTI_UP:
st->t_up = pe_local.timestamp; /* Record recent up event time */
if ((cnt > 1) || /* Ignore if more fingers touch surface */
(!st->t_st_x)) /* IGNORE if info was cleared, long press,move */
return;
if ((pe_local.timestamp - ELM_GESTURE_MOMENTUM_TIMEOUT) > st->t_end)
{ /* Too long of a wait, reset all values */
st->line_st.x = pe_local.x;
st->line_st.y = pe_local.y;
st->t_st_y = st->t_st_x = pe_local.timestamp;
st->xdir = st->ydir = 0;
}
st->info.x2 = pe_local.x;
st->info.y2 = pe_local.y;
st->line_end.x = pe_local.x;
st->line_end.y = pe_local.y;
st->t_end = pe_local.timestamp;
if ((fabs(st->info.mx) > ELM_GESTURE_MINIMUM_MOMENTUM) ||
(fabs(st->info.my) > ELM_GESTURE_MINIMUM_MOMENTUM))
state_to_report = ELM_GESTURE_STATE_END;
else
state_to_report = ELM_GESTURE_STATE_ABORT;
ev_flag = _set_state(gesture, state_to_report, &st->info,
EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
default:
return;
}
}
static int
compare_line_device(const void *data1, const void *data2)
{ /* Compare device component of line struct */
const Line_Data *ln1 = data1;
const int *device = data2;
if (ln1->t_st) /* Compare only with lines that started */
return (ln1->device - (*device));
return (-1);
}
/**
* @internal
*
* This function construct line struct from input.
* @param info pointer to store line momentum.
* @param st line info to store input data.
* @param pe The recent input event as stored in pe struct.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_single_line_process(Elm_Gesture_Line_Info *info, Line_Data *st,
Pointer_Event *pe, Evas_Callback_Type event_type)
{ /* Record events and set momentum for line pointed by st */
if (!pe)
return EINA_FALSE;
switch (event_type)
{
case EVAS_CALLBACK_MOUSE_DOWN:
case EVAS_CALLBACK_MOUSE_MOVE:
case EVAS_CALLBACK_MULTI_DOWN:
case EVAS_CALLBACK_MULTI_MOVE:
if (!st->t_st)
{ /* This happens only when line starts */
st->line_st.x = pe->x;
st->line_st.y = pe->y;
st->t_st = pe->timestamp;
st->device = pe->device;
info->momentum.x1 = pe->x;
info->momentum.y1 = pe->y;
info->momentum.tx = pe->timestamp;
info->momentum.ty = pe->timestamp;
return EINA_TRUE;
}
break;
case EVAS_CALLBACK_MOUSE_UP:
case EVAS_CALLBACK_MULTI_UP:
/* IGNORE if line info was cleared, like long press, move */
if (!st->t_st)
return EINA_FALSE;
st->line_end.x = pe->x;
st->line_end.y = pe->y;
st->t_end = pe->timestamp;
break;
default:
return EINA_FALSE;
}
if (!st->t_st)
{
_line_data_reset(st);
return EINA_FALSE;
}
info->momentum.x2 = pe->x;
info->momentum.y2 = pe->y;
_set_momentum(&info->momentum, st->line_st.x, st->line_st.y, pe->x, pe->y,
st->t_st, st->t_st, pe->timestamp);
return EINA_TRUE;
}
/**
* @internal
*
* This function test for (n) line gesture.
* @param obj The gesture-layer object.
* @param pe The recent input event as stored in pe struct.
* @param event_info Original input event pointer.
* @param event_type Type of original input event.
* @param g_type what Gesture we are testing.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_n_line_test(Evas_Object *obj, Pointer_Event *pe, void *event_info,
Evas_Callback_Type event_type, Elm_Gesture_Type g_type)
{
if (!pe)
return;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
Gesture_Info *gesture = wd->gesture[g_type];
if (!gesture ) return;
/* When continues enable = TRUE a gesture may START on MOVE event */
/* We don't allow this to happen with the if-statement below. */
/* When continues enable = FALSE a gesture may START on DOWN only */
/* Therefor it would NOT start on MOVE event. */
/* NOTE that touched list is updated AFTER this function returns */
/* so (count == 0) when we get here on first touch on surface. */
if ((wd->glayer_continues_enable) && (!eina_list_count(wd->touched)))
return; /* Got move on mouse-over move */
Line_Type *st = gesture->data;
if (!st)
{
st = calloc(1, sizeof(Line_Type));
gesture->data = st;
}
Line_Data *line = NULL;
Eina_List *list = st->list;
unsigned cnt = eina_list_count(list);
if (cnt)
{ /* list is not empty, locate this device on list */
line = (Line_Data *) eina_list_search_unsorted(st->list,
compare_line_device, &pe->device);
}
if (!line)
{ /* List is empty or device not found, new line-struct on START only */
if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
(event_type == EVAS_CALLBACK_MULTI_DOWN) ||
((wd->glayer_continues_enable) && /* START on MOVE also */
((event_type == EVAS_CALLBACK_MOUSE_MOVE) ||
(event_type == EVAS_CALLBACK_MULTI_MOVE))))
{ /* Allocate new item on START only */
line = calloc(1, sizeof(Line_Data));
_line_data_reset(line);
list = eina_list_append(list, line);
st->list = list;
}
}
if (!line) /* This may happen on MOVE that comes before DOWN */
return; /* No line-struct to work with, can't continue testing */
if (_single_line_process(&st->info, line, pe, event_type)) /* update st with input */
consume_event(wd, event_info, event_type, EVAS_EVENT_FLAG_NONE);
/* Get direction and magnitude of the line */
double angle;
get_vector(line->line_st.x, line->line_st.y, pe->x, pe->y,
&line->line_length, &angle);
/* These are used later to compare lines length */
Evas_Coord shortest_line_len = line->line_length;
Evas_Coord longest_line_len = line->line_length;
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
/* Now update line-state */
if (line->t_st)
{ /* Analyze line only if line started */
if (line->line_angle >= 0.0)
{ /* if line direction was set, we test if broke tolerance */
double a = fabs(angle - line->line_angle);
double d = (tan(DEG2RAD(a))) * line->line_length; /* Distance from line */
#if defined(DEBUG_GESTURE_LAYER)
printf("%s a=<%f> d=<%f>\n", __func__, a, d);
#endif
if ((d > wd->line_distance_tolerance) || (a > wd->line_angular_tolerance))
{ /* Broke tolerance: abort line and start a new one */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
if (wd->glayer_continues_enable)
{ /* We may finish line if momentum is zero */
/* This is for continues-gesture */
if ((!st->info.momentum.mx) && (!st->info.momentum.my))
{ /* Finish line on zero momentum for continues gesture */
line->line_end.x = pe->x;
line->line_end.y = pe->y;
line->t_end = pe->timestamp;
}
}
}
else
{ /* Record the line angle as it broke minimum length for line */
if (line->line_length >= wd->line_min_length)
st->info.angle = line->line_angle = angle;
}
if (line->t_end)
{
if (line->line_angle < 0.0)
{ /* it's not a line, too short more close to a tap */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
}
}
/* Count how many lines already started / ended */
int started = 0;
int ended = 0;
unsigned int tm_start = pe->timestamp;
unsigned int tm_end = pe->timestamp;
Eina_List *l;
Line_Data *t_line;
double base_angle = ELM_GESTURE_NEGATIVE_ANGLE;
Eina_Bool lines_parallel = EINA_TRUE;
EINA_LIST_FOREACH(list, l, t_line)
{
if (base_angle < 0)
base_angle = t_line->line_angle;
else
{
if (t_line->line_angle >= 0)
{ /* Compare angle only with lines with direction defined */
if (fabs(base_angle - t_line->line_angle) >
wd->line_angular_tolerance)
lines_parallel = EINA_FALSE;
}
}
if (t_line->line_length)
{ /* update only if this line is used */
if (shortest_line_len > t_line->line_length)
shortest_line_len = t_line->line_length;
if (longest_line_len < t_line->line_length)
longest_line_len = t_line->line_length;
}
if (t_line->t_st)
{
started++;
if (t_line->t_st < tm_start)
tm_start = t_line->t_st;
}
if (t_line->t_end)
{
ended++;
if (t_line->t_end < tm_end)
tm_end = t_line->t_end;
}
}
st->info.momentum.n = started;
if (ended &&
((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
(event_type == EVAS_CALLBACK_MULTI_DOWN)))
{ /* user lift one finger then starts again without line-end - ABORT */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
if (!lines_parallel)
{ /* Lines are NOT at same direction, abort this gesture */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
/* We report ABORT if lines length are NOT matching when fingers are up */
if ((longest_line_len - shortest_line_len) > (_elm_config->finger_size * 2))
{
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
if ((g_type == ELM_GESTURE_N_FLICKS) && ((tm_end - tm_start) > wd->flick_time_limit_ms))
{ /* We consider FLICK as a fast line.ABORT if take too long to finish */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
switch (event_type)
{
case EVAS_CALLBACK_MOUSE_UP:
case EVAS_CALLBACK_MULTI_UP:
if ((started) && (started == ended))
{
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_END,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
return;
case EVAS_CALLBACK_MOUSE_DOWN:
case EVAS_CALLBACK_MULTI_DOWN:
case EVAS_CALLBACK_MOUSE_MOVE:
case EVAS_CALLBACK_MULTI_MOVE:
if (started)
{
if (wd->glayer_continues_enable && (started == ended))
{ /* For continues gesture */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_END,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
else
{ /* When continues, may START on MOVE event too */
Elm_Gesture_State s = ELM_GESTURE_STATE_MOVE;
/* This happens when: on n > 1 lines then one finger up */
/* caused abort, then put finger down. */
/* This will stop line from starting again. */
/* Number of lines, MUST match touched-device in list */
if ((!wd->glayer_continues_enable) &&
(eina_list_count(st->list) < eina_list_count(wd->touched)))
s = ELM_GESTURE_STATE_ABORT;
if (gesture->state == ELM_GESTURE_STATE_UNDEFINED)
s = ELM_GESTURE_STATE_START;
ev_flag = _set_state(gesture, s, &st->info, EINA_TRUE);
consume_event(wd, event_info, event_type, ev_flag);
}
}
break;
default:
return; /* Unhandeld event type */
}
}
/**
* @internal
*
* This function is used to check if rotation gesture started.
* @param st Contains current rotation values from user input.
* @return TRUE/FALSE if we need to set rotation START.
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
rotation_broke_tolerance(Rotate_Type *st)
{
if (st->info.base_angle < 0)
return EINA_FALSE; /* Angle has to be computed first */
if (st->rotate_angular_tolerance < 0)
return EINA_TRUE;
double low = st->info.base_angle - st->rotate_angular_tolerance;
double high = st->info.base_angle + st->rotate_angular_tolerance;
double t = st->info.angle;
if (low < 0)
{
low += 180;
high += 180;
if (t < 180)
t += 180;
else
t -= 180;
}
if (high > 360)
{
low -= 180;
high -= 180;
if (t < 180)
t += 180;
else
t -= 180;
}
#if defined(DEBUG_GESTURE_LAYER)
printf("%s angle=<%f> low=<%f> high=<%f>\n", __func__, t, low, high);
#endif
if ((t < low) || (t > high))
{ /* This marks that roation action has started */
st->rotate_angular_tolerance = ELM_GESTURE_NEGATIVE_ANGLE;
st->info.base_angle = st->info.angle; /* Avoid jump in angle value */
return EINA_TRUE;
}
return EINA_FALSE;
}
/**
* @internal
*
* This function is used for computing the gap between fingers.
* It returns the length and center point between fingers.
*
* @param x1 first finger x location.
* @param y1 first finger y location.
* @param x2 second finger x location.
* @param y2 second finger y location.
* @param x Gets center point x cord (output)
* @param y Gets center point y cord (output)
*
* @return length of the line between (x1,y1), (x2,y2) in pixels.
*
* @ingroup Elm_Gesture_Layer
*/
static Evas_Coord
2012-03-07 00:19:55 -08:00
get_finger_gap_length(Evas_Coord xx1, Evas_Coord yy1,
Evas_Coord xx2, Evas_Coord yy2,
Evas_Coord *x, Evas_Coord *y)
{
double a, b, xx, yy, gap;
2012-03-07 00:19:55 -08:00
xx = fabs(xx2 - xx1);
yy = fabs(yy2 - yy1);
gap = sqrt((xx * xx) + (yy * yy));
/* START - Compute zoom center point */
/* The triangle defined as follows:
* B
* / |
* / |
* gap / | a
* / |
* A-----C
* b
* http://en.wikipedia.org/wiki/Trigonometric_functions
*************************************/
2012-03-07 00:19:55 -08:00
if (((int)xx) && ((int)yy))
{
double A = atan((yy / xx));
#if defined(DEBUG_GESTURE_LAYER)
printf("xx=<%f> yy=<%f> A=<%f>\n", xx, yy, A);
#endif
a = (Evas_Coord) ((gap / 2) * sin(A));
b = (Evas_Coord) ((gap / 2) * cos(A));
2012-03-07 00:19:55 -08:00
*x = (Evas_Coord) ((xx2 > xx1) ? (xx1 + b) : (xx2 + b));
*y = (Evas_Coord) ((yy2 > yy1) ? (yy1 + a) : (yy2 + a));
}
else
{
2012-03-07 00:19:55 -08:00
if ((int)xx)
{ /* horiz line, take half width */
#if defined(DEBUG_GESTURE_LAYER)
printf("==== HORIZ ====\n");
#endif
2012-03-07 00:19:55 -08:00
*x = (Evas_Coord) ((xx1 + xx2) / 2);
*y = (Evas_Coord) (yy1);
}
2012-03-07 00:19:55 -08:00
if ((int)yy)
{ /* vert line, take half width */
#if defined(DEBUG_GESTURE_LAYER)
printf("==== VERT ====\n");
#endif
2012-03-07 00:19:55 -08:00
*x = (Evas_Coord) (xx1);
*y = (Evas_Coord) ((yy1 + yy2) / 2);
}
}
/* END - Compute zoom center point */
return (Evas_Coord) gap;
}
/**
* @internal
*
* This function is used for computing zoom value.
*
* @param st Pointer to zoom data based on user input.
* @param tm_end Recent input event timestamp.
* @param zoom_val Current computed zoom value.
*
* @return zoom momentum
*
* @ingroup Elm_Gesture_Layer
*/
static double
_zoom_momentum_get(Zoom_Type *st, unsigned int tm_end, double zoom_val)
{
unsigned int tm_total;
if (!st->m_st_tm)
{ /* Init, and we don't start computing momentum yet */
st->m_st_tm = st->m_prev_tm = tm_end;
st->m_base = zoom_val;
return 0.0;
}
if ((tm_end - ELM_GESTURE_MOMENTUM_DELAY) < st->m_st_tm)
return 0.0; /* we don't start to compute momentum yet */
if (st->dir)
{ /* if direction was already defined, check if changed */
if (((st->dir < 0) && (zoom_val > st->info.zoom)) ||
((st->dir > 0) && (zoom_val < st->info.zoom)))
{ /* Direction changed, reset momentum */
st->m_st_tm = 0;
st->dir = (-st->dir);
return 0.0;
}
}
else
st->dir = (zoom_val > st->info.zoom) ? 1 : -1; /* init */
if ((tm_end - ELM_GESTURE_MOMENTUM_TIMEOUT) > st->m_prev_tm)
{
st->m_st_tm = 0; /* Rest momentum when waiting too long */
return 0.0;
}
st->m_prev_tm = tm_end;
tm_total = tm_end - st->m_st_tm;
if (tm_total)
return ((zoom_val - st->m_base) * 1000) / tm_total;
else
return 0.0;
}
/**
* @internal
*
* This function is used for computing zoom value.
*
* @param st Pointer to zoom data based on user input.
* @param x1 first finger x location.
* @param y1 first finger y location.
* @param x2 second finger x location.
* @param y2 second finger y location.
* @param factor zoom-factor, used to determine how fast zoom works.
*
* @return zoom value, when 1.0 means no zoom, 0.5 half size...
*
* @ingroup Elm_Gesture_Layer
*/
static double
2012-03-07 00:21:20 -08:00
compute_zoom(Zoom_Type *st,
Evas_Coord xx1, Evas_Coord yy1,
Evas_Coord xx2, Evas_Coord yy2,
double zoom_finger_factor)
{
double rt = 1.0;
unsigned int tm_end = (st->zoom_mv.timestamp > st->zoom_mv1.timestamp) ?
2012-03-07 00:21:20 -08:00
st->zoom_mv.timestamp : st->zoom_mv1.timestamp;
2012-03-07 00:21:20 -08:00
Evas_Coord diam = get_finger_gap_length(xx1, yy1, xx2, yy2,
&st->info.x, &st->info.y);
st->info.radius = diam / 2;
if (!st->zoom_base)
{
st->zoom_base = diam;
return st->info.zoom;
}
if (st->zoom_distance_tolerance)
{ /* zoom tolerance <> ZERO, means zoom action NOT started yet */
if (diam < (st->zoom_base - st->zoom_distance_tolerance))
{ /* avoid jump with zoom value when break tolerance */
st->zoom_base -= st->zoom_distance_tolerance;
st->zoom_distance_tolerance = 0;
}
if (diam > (st->zoom_base + st->zoom_distance_tolerance))
{ /* avoid jump with zoom value when break tolerance */
st->zoom_base += st->zoom_distance_tolerance;
st->zoom_distance_tolerance = 0;
}
return rt;
}
/* We use factor only on the difference between gap-base */
/* if gap=120, base=100, we get ((120-100)/100)=0.2*factor */
rt = ((1.0) + ((((float) diam - (float) st->zoom_base) /
2012-03-07 00:21:20 -08:00
(float) st->zoom_base) * zoom_finger_factor));
/* Momentum: zoom per second: */
st->info.momentum = _zoom_momentum_get(st, tm_end, rt);
return rt;
}
/**
* @internal
*
* This function handles zoom with mouse wheel.
* thats a combination of wheel + CTRL key.
* @param obj The gesture-layer object.
* @param event_info Original input event pointer.
* @param event_type Type of original input event.
* @param g_type what Gesture we are testing.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_zoom_with_wheel_test(Evas_Object *obj, void *event_info,
Evas_Callback_Type event_type, Elm_Gesture_Type g_type)
{
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (!wd->gesture[g_type]) return;
Gesture_Info *gesture_zoom = wd->gesture[g_type];
Zoom_Type *st = gesture_zoom->data;
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
if (!st)
{ /* Allocated once on first time, used for zoom intermediate data */
st = calloc(1, sizeof(Zoom_Type));
gesture_zoom->data = st;
_zoom_test_reset(gesture_zoom);
}
switch (event_type)
{
case EVAS_CALLBACK_KEY_UP:
{
Evas_Event_Key_Up *p = event_info;
if ((!strcmp(p->keyname, "Control_L")) ||
(!strcmp(p->keyname, "Control_R")))
{ /* Test if we ended a zoom gesture when releasing CTRL */
if ((st->zoom_wheel) &&
((gesture_zoom->state == ELM_GESTURE_STATE_START) ||
(gesture_zoom->state == ELM_GESTURE_STATE_MOVE)))
{ /* User released CTRL after zooming */
st->info.momentum = _zoom_momentum_get(st,
p->timestamp, st->info.zoom);
ev_flag = _set_state(gesture_zoom,
ELM_GESTURE_STATE_END, &st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
}
break;
}
case EVAS_CALLBACK_MOUSE_WHEEL:
{
Eina_Bool force;
Elm_Gesture_State s;
if (!evas_key_modifier_is_set(
((Evas_Event_Mouse_Wheel *) event_info)->modifiers,
"Control"))
{ /* if using wheel witout CTRL after starting zoom */
if ((st->zoom_wheel) &&
((gesture_zoom->state == ELM_GESTURE_STATE_START) ||
(gesture_zoom->state == ELM_GESTURE_STATE_MOVE)))
{
ev_flag = _set_state(gesture_zoom,
ELM_GESTURE_STATE_END, &st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
else
return; /* Ignore mouse-wheel without control */
}
/* Using mouse wheel with CTRL for zoom */
if (st->zoom_wheel || (st->zoom_distance_tolerance == 0))
{ /* (zoom_wheel == NULL) and (zoom_distance_tolerance == 0)
we continue a zoom gesture */
force = EINA_TRUE;
s = ELM_GESTURE_STATE_MOVE;
}
else
{ /* On first wheel event, report START */
Evas_Modifier_Mask mask = evas_key_modifier_mask_get(
evas_object_evas_get(wd->target), "Control");
force = EINA_FALSE;
s = ELM_GESTURE_STATE_START;
if (!evas_object_key_grab(wd->target, "Control_L", mask, 0, EINA_FALSE))
ERR("Failed to Grabbed CTRL_L");
if (!evas_object_key_grab(wd->target, "Control_R", mask, 0, EINA_FALSE))
ERR("Failed to Grabbed CTRL_R");
}
st->zoom_distance_tolerance = 0; /* Cancel tolerance */
st->zoom_wheel = (Evas_Event_Mouse_Wheel *) event_info;
st->info.x = st->zoom_wheel->canvas.x;
st->info.y = st->zoom_wheel->canvas.y;
if (st->zoom_wheel->z < 0) /* zoom in */
st->info.zoom += (wd->zoom_finger_factor * wd->zoom_wheel_factor);
if (st->zoom_wheel->z > 0) /* zoom out */
st->info.zoom -= (wd->zoom_finger_factor * wd->zoom_wheel_factor);
if (st->info.zoom < 0.0)
st->info.zoom = 0.0;
st->info.momentum = _zoom_momentum_get(st,
st->zoom_wheel->timestamp, st->info.zoom);
ev_flag = _set_state(gesture_zoom, s, &st->info, force);
consume_event(wd, event_info, event_type, ev_flag);
break;
}
default:
return;
}
}
/**
* @internal
*
* This function is used to test zoom gesture.
* user may combine zoom, rotation together.
* so its possible that both will be detected from input.
* (both are two-finger movement-oriented gestures)
*
* @param obj The gesture-layer object.
* @param event_info Pointer to recent input event.
* @param event_type Recent input event type.
* @param g_type what Gesture we are testing.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_zoom_test(Evas_Object *obj, Pointer_Event *pe, void *event_info,
Evas_Callback_Type event_type, Elm_Gesture_Type g_type)
{
if (!pe)
return;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (!wd->gesture[g_type]) return;
Gesture_Info *gesture_zoom = wd->gesture[g_type];
Zoom_Type *st = gesture_zoom->data;
if (!st)
{ /* Allocated once on first time, used for zoom data */
st = calloc(1, sizeof(Zoom_Type));
gesture_zoom->data = st;
_zoom_test_reset(gesture_zoom);
}
/* Start - new zoom testing, letting all fingers start */
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
switch (event_type)
{
case EVAS_CALLBACK_MOUSE_MOVE:
case EVAS_CALLBACK_MULTI_MOVE:
/* if non-continues mode and gesture NOT started, ignore MOVE */
if ((!wd->glayer_continues_enable) &&
(!st->zoom_st.timestamp))
return;
case EVAS_CALLBACK_MOUSE_DOWN:
case EVAS_CALLBACK_MULTI_DOWN:
{ /* Here we take care of zoom-start and zoom move */
Eina_List *l;
Pointer_Event *p;
if (eina_list_count(wd->touched) > 2)
{ /* Process zoom only when 2 fingers on surface */
ev_flag = _set_state(gesture_zoom,
ELM_GESTURE_STATE_ABORT, &st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
if (!st->zoom_st.timestamp)
{ /* Now scan touched-devices list and find other finger */
EINA_LIST_FOREACH(wd->touched, l, p)
{ /* Device of other finger <> pe device */
if (p->device != pe->device)
break;
}
if (!p) /* Single finger on touch */
return;
/* Record down fingers */
consume_event(wd, event_info, event_type, ev_flag);
memcpy(&st->zoom_st, pe, sizeof(Pointer_Event));
memcpy(&st->zoom_st1, p, sizeof(Pointer_Event));
/* Set mv field as well to be ready for MOVE events */
memcpy(&st->zoom_mv, pe, sizeof(Pointer_Event));
memcpy(&st->zoom_mv1, p, sizeof(Pointer_Event));
/* Here we have zoom_st, zoom_st1 set, report START */
/* Set zoom-base after BOTH down events recorded */
/* Compute length of line between fingers zoom start */
st->info.zoom = 1.0;
st->zoom_base = get_finger_gap_length(st->zoom_st1.x,
st->zoom_st1.y, st->zoom_st.x, st->zoom_st.y,
&st->info.x, &st->info.y);
st->info.radius = st->zoom_base / 2;
if ((gesture_zoom->state != ELM_GESTURE_STATE_START) &&
(gesture_zoom->state != ELM_GESTURE_STATE_MOVE))
{ /* zoom started with mouse-wheel, don't report twice */
ev_flag = _set_state(gesture_zoom,
ELM_GESTURE_STATE_START, &st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
return; /* Zoom started */
} /* End of ZOOM_START handling */
/* if we got here, we have (exacally) two fingers on surfce */
/* we also after START, report MOVE */
/* First detect which finger moved */
if (pe->device == st->zoom_mv.device)
memcpy(&st->zoom_mv, pe, sizeof(Pointer_Event));
else if (pe->device == st->zoom_mv1.device)
memcpy(&st->zoom_mv1, pe, sizeof(Pointer_Event));
/* Compute change in zoom as fingers move */
st->info.zoom = compute_zoom(st,
st->zoom_mv.x, st->zoom_mv.y,
st->zoom_mv1.x, st->zoom_mv1.y,
wd->zoom_finger_factor);
if (!st->zoom_distance_tolerance)
{ /* Zoom broke tolerance, report move */
double d = st->info.zoom - st->next_step;
if (d < 0.0)
d = (-d);
if (d >= wd->zoom_step)
{ /* Report move in steps */
st->next_step = st->info.zoom;
ev_flag = _set_state(gesture_zoom,
ELM_GESTURE_STATE_MOVE,
&st->info, EINA_TRUE);
consume_event(wd, event_info, event_type, ev_flag);
}
} /* End of ZOOM_MOVE handling */
return;
}
case EVAS_CALLBACK_MOUSE_UP:
case EVAS_CALLBACK_MULTI_UP:
/* Reset timestamp of finger-up.This is used later
by _zoom_test_reset() to retain finger-down data */
consume_event(wd, event_info, event_type, ev_flag);
if (((st->zoom_wheel) || (st->zoom_base)) &&
(st->zoom_distance_tolerance == 0))
{
ev_flag = _set_state(gesture_zoom, ELM_GESTURE_STATE_END,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
/* if we got here not a ZOOM */
if (gesture_zoom->state != ELM_GESTURE_STATE_UNDEFINED)
{ /* Must be != undefined, if gesture started */
ev_flag = _set_state(gesture_zoom,
ELM_GESTURE_STATE_ABORT, &st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
_zoom_test_reset(gesture_zoom);
return;
default:
return;
}
}
static void
_get_rotate_properties(Rotate_Type *st,
2012-03-07 00:21:20 -08:00
Evas_Coord xx1, Evas_Coord yy1,
Evas_Coord xx2, Evas_Coord yy2,
double *angle)
{ /* FIXME: Fix momentum computation, it's wrong */
double prev_angle = *angle;
2012-03-07 00:21:20 -08:00
st->info.radius = get_finger_gap_length(xx1, yy1, xx2, yy2,
&st->info.x, &st->info.y) / 2;
2012-03-07 00:21:20 -08:00
*angle = get_angle(xx1, yy1, xx2, yy2);
if (angle == &st->info.angle)
{ /* Fingers are moving, compute momentum */
unsigned int tm_start =
(st->rotate_st.timestamp > st->rotate_st1.timestamp)
? st->rotate_st.timestamp : st->rotate_st1.timestamp;
unsigned int tm_end =
(st->rotate_mv.timestamp > st->rotate_mv1.timestamp)
? st->rotate_mv.timestamp : st->rotate_mv1.timestamp;
unsigned int tm_total = tm_end - tm_start;
if (tm_total)
{ /* Momentum computed as:
accumulated roation angle (deg) divided by time */
double m = 0;;
if (((prev_angle < 90) && ((*angle) > 270)) ||
((prev_angle > 270) && ((*angle) < 90)))
{ /* We circle passing ZERO point */
prev_angle = (*angle);
}
else m = prev_angle - (*angle);
st->accum_momentum += m;
if ((tm_end - st->prev_momentum_tm) < 100)
st->prev_momentum += m;
else
{
if (fabs(st->prev_momentum) < 0.002)
st->accum_momentum = 0.0; /* reset momentum */
st->prev_momentum = 0.0; /* Start again */
}
st->prev_momentum_tm = tm_end;
st->info.momentum = (st->accum_momentum * 1000) / tm_total;
}
}
else
st->info.momentum = 0;
}
/**
* @internal
*
* This function is used to test rotation gesture.
* user may combine zoom, rotation together.
* so its possible that both will be detected from input.
* (both are two-finger movement-oriented gestures)
*
* @param obj The gesture-layer object.
* @param event_info Pointer to recent input event.
* @param event_type Recent input event type.
* @param g_type what Gesture we are testing.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_rotate_test(Evas_Object *obj, Pointer_Event *pe, void *event_info,
Evas_Callback_Type event_type, Elm_Gesture_Type g_type)
{
if (!pe)
return;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (!wd->gesture[g_type]) return;
Gesture_Info *gesture = wd->gesture[g_type];
Rotate_Type *st;
if (gesture)
{
st = gesture->data;
if (!st)
{ /* Allocated once on first time */
st = calloc(1, sizeof(Rotate_Type));
gesture->data = st;
_rotate_test_reset(gesture);
}
}
Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
switch (event_type)
{
case EVAS_CALLBACK_MOUSE_MOVE:
case EVAS_CALLBACK_MULTI_MOVE:
/* if non-continues mode and gesture NOT started, ignore MOVE */
if ((!wd->glayer_continues_enable) &&
(!st->rotate_st.timestamp))
return;
case EVAS_CALLBACK_MOUSE_DOWN:
case EVAS_CALLBACK_MULTI_DOWN:
{ /* Here we take care of rotate-start and rotate move */
Eina_List *l;
Pointer_Event *p;
if (eina_list_count(wd->touched) > 2)
{ /* Process rotate only when 2 fingers on surface */
ev_flag = _set_state(gesture,
ELM_GESTURE_STATE_ABORT, &st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
if (!st->rotate_st.timestamp)
{ /* Now scan touched-devices list and find other finger */
EINA_LIST_FOREACH(wd->touched, l, p)
{ /* Device of other finger <> pe device */
if (p->device != pe->device)
break;
}
if (!p)
return; /* Single finger on touch */
/* Record down fingers */
consume_event(wd, event_info, event_type, ev_flag);
memcpy(&st->rotate_st, pe, sizeof(Pointer_Event));
memcpy(&st->rotate_st1, p, sizeof(Pointer_Event));
/* Set mv field as well to be ready for MOVE events */
memcpy(&st->rotate_mv, pe, sizeof(Pointer_Event));
memcpy(&st->rotate_mv1, p, sizeof(Pointer_Event));
/* Here we have rotate_st, rotate_st1 set, report START */
/* Set rotate-base after BOTH down events recorded */
/* Compute length of line between fingers rotate start */
_get_rotate_properties(st,
st->rotate_st.x, st->rotate_st.y,
st->rotate_st1.x, st->rotate_st1.y,
&st->info.base_angle);
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_START,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return; /* Rotate started */
} /* End of ROTATE_START handling */
/* if we got here, we have (exacally) two fingers on surfce */
/* we also after START, report MOVE */
/* First detect which finger moved */
if (pe->device == st->rotate_mv.device)
memcpy(&st->rotate_mv, pe, sizeof(Pointer_Event));
else if (pe->device == st->rotate_mv1.device)
memcpy(&st->rotate_mv1, pe, sizeof(Pointer_Event));
/* Compute change in rotate as fingers move */
_get_rotate_properties(st,
st->rotate_mv.x, st->rotate_mv.y,
st->rotate_mv1.x, st->rotate_mv1.y,
&st->info.angle);
if (rotation_broke_tolerance(st))
{ /* Rotation broke tolerance, report move */
double d = st->info.angle - st->next_step;
if (d < 0)
d = (-d);
if (d >= wd->rotate_step)
{ /* Report move in steps */
st->next_step = st->info.angle;
ev_flag = _set_state(gesture,
ELM_GESTURE_STATE_MOVE, &st->info, EINA_TRUE);
consume_event(wd, event_info, event_type, ev_flag);
}
} /* End of ROTATE_MOVE handling */
return;
}
case EVAS_CALLBACK_MOUSE_UP:
case EVAS_CALLBACK_MULTI_UP:
consume_event(wd, event_info, event_type, ev_flag);
/* Reset timestamp of finger-up.This is used later
by rotate_test_reset() to retain finger-down data */
if (st->rotate_angular_tolerance < 0)
{
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_END,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
return;
}
if (gesture->state != ELM_GESTURE_STATE_UNDEFINED)
{ /* Must be != undefined, if gesture started */
ev_flag = _set_state(gesture, ELM_GESTURE_STATE_ABORT,
&st->info, EINA_FALSE);
consume_event(wd, event_info, event_type, ev_flag);
}
_rotate_test_reset(gesture);
return;
default:
return;
}
}
/**
* @internal
*
* This function is used to save input events in an abstract struct
* to be used later by getsure-testing functions.
*
* @param data The gesture-layer object.
* @param event_info Pointer to recent input event.
* @param event_type Recent input event type.
* @param pe The abstract data-struct (output).
*
* @ingroup Elm_Gesture_Layer
*/
static Eina_Bool
_make_pointer_event(void *data, void *event_info,
Evas_Callback_Type event_type, Pointer_Event *pe)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return EINA_FALSE;
memset(pe, '\0', sizeof(*pe));
switch (event_type)
{
case EVAS_CALLBACK_MOUSE_DOWN:
pe->x = ((Evas_Event_Mouse_Down *) event_info)->canvas.x;
pe->y = ((Evas_Event_Mouse_Down *) event_info)->canvas.y;
pe->timestamp = ((Evas_Event_Mouse_Down *) event_info)->timestamp;
pe->device = ELM_MOUSE_DEVICE;
break;
case EVAS_CALLBACK_MOUSE_UP:
pe->x = ((Evas_Event_Mouse_Up *) event_info)->canvas.x;
pe->y = ((Evas_Event_Mouse_Up *) event_info)->canvas.y;
pe->timestamp = ((Evas_Event_Mouse_Up *) event_info)->timestamp;
pe->device = ELM_MOUSE_DEVICE;
break;
case EVAS_CALLBACK_MOUSE_MOVE:
pe->x = ((Evas_Event_Mouse_Move *) event_info)->cur.canvas.x;
pe->y = ((Evas_Event_Mouse_Move *) event_info)->cur.canvas.y;
pe->timestamp = ((Evas_Event_Mouse_Move *) event_info)->timestamp;
pe->device = ELM_MOUSE_DEVICE;
break;
case EVAS_CALLBACK_MULTI_DOWN:
pe->x = ((Evas_Event_Multi_Down *) event_info)->canvas.x;
pe->y = ((Evas_Event_Multi_Down *) event_info)->canvas.y;
pe->timestamp = ((Evas_Event_Multi_Down *) event_info)->timestamp;
pe->device = ((Evas_Event_Multi_Down *) event_info)->device;
break;
case EVAS_CALLBACK_MULTI_UP:
pe->x = ((Evas_Event_Multi_Up *) event_info)->canvas.x;
pe->y = ((Evas_Event_Multi_Up *) event_info)->canvas.y;
pe->timestamp = ((Evas_Event_Multi_Up *) event_info)->timestamp;
pe->device = ((Evas_Event_Multi_Up *) event_info)->device;
break;
case EVAS_CALLBACK_MULTI_MOVE:
pe->x = ((Evas_Event_Multi_Move *) event_info)->cur.canvas.x;
pe->y = ((Evas_Event_Multi_Move *) event_info)->cur.canvas.y;
pe->timestamp = ((Evas_Event_Multi_Move *) event_info)->timestamp;
pe->device = ((Evas_Event_Multi_Move *) event_info)->device;
break;
default:
return EINA_FALSE;
}
pe->event_type = event_type;
return EINA_TRUE;
}
/**
* @internal
*
* This function restartes line, flick, zoom and rotate gestures
* when gesture-layer continues-gestures enabled.
* Example of continues-gesture:
* When doing a line, user stops moving finger but keeps fingers on touch.
* This will cause line-end, then as user continues moving his finger
* it re-starts line gesture.
* When continue mode is disabled, user has to lift finger from touch
* to end a gesture. Them touch-again to start a new one.
*
* @param data The gesture-layer object.
* @param wd gesture layer widget data.
* @param states_reset flag that marks gestures were reset in history clear.
*
* @ingroup Elm_Gesture_Layer
*/
static void
continues_gestures_restart(void *data, Eina_Bool states_reset)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
/* Run through events to restart gestures */
Gesture_Info *g;
Eina_Bool n_momentum, n_lines, n_flicks, zoom, rotate;
/* We turn-on flag for finished, aborted, not-started gestures */
g = wd->gesture[ELM_GESTURE_MOMENTUM];
n_momentum = (g) ? ((states_reset) | ((g->state != ELM_GESTURE_STATE_START)
&& (g->state != ELM_GESTURE_STATE_MOVE))) : EINA_FALSE;
if (n_momentum)
{
_momentum_test_reset(wd->gesture[ELM_GESTURE_MOMENTUM]);
_set_state(g, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
SET_TEST_BIT(g);
}
g = wd->gesture[ELM_GESTURE_N_LINES];
n_lines = (g) ? ((states_reset) | ((g->state != ELM_GESTURE_STATE_START)
&& (g->state != ELM_GESTURE_STATE_MOVE))) : EINA_FALSE;
if (n_lines)
{
_line_test_reset(wd->gesture[ELM_GESTURE_N_LINES]);
_set_state(g, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
SET_TEST_BIT(g);
}
g = wd->gesture[ELM_GESTURE_N_FLICKS];
n_flicks = (g) ? ((states_reset) | ((g->state != ELM_GESTURE_STATE_START)
&& (g->state != ELM_GESTURE_STATE_MOVE))) : EINA_FALSE;
if (n_flicks)
{
_line_test_reset(wd->gesture[ELM_GESTURE_N_FLICKS]);
_set_state(g, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
SET_TEST_BIT(g);
}
g = wd->gesture[ELM_GESTURE_ZOOM];
zoom = (g) ? ((states_reset) | ((g->state != ELM_GESTURE_STATE_START)
&& (g->state != ELM_GESTURE_STATE_MOVE))) : EINA_FALSE;
if (zoom)
{
_zoom_test_reset(wd->gesture[ELM_GESTURE_ZOOM]);
_set_state(g, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
SET_TEST_BIT(g);
}
g = wd->gesture[ELM_GESTURE_ROTATE];
rotate = (g) ? ((states_reset) | ((g->state != ELM_GESTURE_STATE_START)
&& (g->state != ELM_GESTURE_STATE_MOVE))) : EINA_FALSE;
if (rotate)
{
_rotate_test_reset(wd->gesture[ELM_GESTURE_ROTATE]);
_set_state(g, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
SET_TEST_BIT(g);
}
}
/**
* @internal
*
* This function the core-function where input handling is done.
* Here we get user input and stream it to gesture testing.
* We notify user about any gestures with new state:
* Valid states are:
* START - gesture started.
* MOVE - gesture is ongoing.
* END - gesture was completed.
* ABORT - gesture was aborted after START, MOVE (will NOT be completed)
*
* We also check if a gesture was detected, then reset event history
* If no gestures were found we reset gesture test flag
* after streaming event-history to widget.
* (stream to the widget all events not consumed as a gesture)
*
* @param data The gesture-layer object.
* @param event_info Pointer to recent input event.
* @param event_type Recent input event type.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_event_process(void *data, Evas_Object *obj __UNUSED__,
void *event_info, Evas_Callback_Type event_type)
{
Pointer_Event _pe;
Pointer_Event *pe = NULL;
Widget_Data *wd = elm_widget_data_get(data);
#if defined(DEBUG_GESTURE_LAYER)
int i;
Gesture_Info *g;
printf("Gesture | State | is tested\n");
for (i = ELM_GESTURE_N_TAPS; i < ELM_GESTURE_LAST; i++)
{
g = wd->gesture[i];
if (g)
printf(" %d %d %d\n", i, g->state, g->test);
}
#endif
/* Start testing candidate gesture from here */
if (_make_pointer_event(data, event_info, event_type, &_pe))
pe = &_pe;
if (IS_TESTED(ELM_GESTURE_N_LONG_TAPS))
_n_long_tap_test(data, pe, event_info, event_type,
ELM_GESTURE_N_LONG_TAPS);
/* This takes care of single, double and tripple tap */
_tap_gestures_test(data, pe, event_info, event_type);
if (IS_TESTED(ELM_GESTURE_MOMENTUM))
_momentum_test(data, pe, event_info, event_type,
ELM_GESTURE_MOMENTUM);
if (IS_TESTED(ELM_GESTURE_N_LINES))
_n_line_test(data, pe, event_info, event_type, ELM_GESTURE_N_LINES);
if (IS_TESTED(ELM_GESTURE_N_FLICKS))
_n_line_test(data, pe, event_info, event_type, ELM_GESTURE_N_FLICKS);
if (_elm_config->glayer_zoom_finger_enable && IS_TESTED(ELM_GESTURE_ZOOM))
_zoom_test(data, pe, event_info, event_type, ELM_GESTURE_ZOOM);
if (IS_TESTED(ELM_GESTURE_ZOOM))
_zoom_with_wheel_test(data, event_info, event_type, ELM_GESTURE_ZOOM);
if (_elm_config->glayer_rotate_finger_enable && IS_TESTED(ELM_GESTURE_ROTATE))
_rotate_test(data, pe, event_info, event_type, ELM_GESTURE_ROTATE);
if (_get_event_flag(event_info, event_type) & EVAS_EVENT_FLAG_ON_HOLD)
_event_history_add(data, event_info, event_type);
/* we maintain list of touched devices */
/* We also use move to track current device x.y pos */
if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
(event_type == EVAS_CALLBACK_MULTI_DOWN) ||
(event_type == EVAS_CALLBACK_MOUSE_MOVE) ||
(event_type == EVAS_CALLBACK_MULTI_MOVE))
{
wd->touched = _add_touched_device(wd->touched, pe);
}
else if ((event_type == EVAS_CALLBACK_MOUSE_UP) ||
(event_type == EVAS_CALLBACK_MULTI_UP))
{
wd->touched = _remove_touched_device(wd->touched, pe);
}
/* Report current states and clear history if needed */
Eina_Bool states_reset = _clear_if_finished(data);
if (wd->glayer_continues_enable)
continues_gestures_restart(data, states_reset);
}
/**
* For all _mouse_* / multi_* functions wethen send this event to
* _event_process function.
*
* @param data The gesture-layer object.
* @param event_info Pointer to recent input event.
*
* @ingroup Elm_Gesture_Layer
*/
static void
_mouse_down(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
if (((Evas_Event_Mouse_Down *) event_info)->button != 1)
return; /* We only process left-click at the moment */
_event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_DOWN);
}
static void
_mouse_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_MOVE);
}
static void
_key_down_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_KEY_DOWN);
}
static void
_key_up_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_KEY_UP);
}
static void
_mouse_up(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
if (((Evas_Event_Mouse_Up *) event_info)->button != 1)
return; /* We only process left-click at the moment */
_event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_UP);
}
static void
_mouse_wheel(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_WHEEL);
}
static void
_multi_down(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_MULTI_DOWN);
}
static void
_multi_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_MULTI_MOVE);
}
static void
_multi_up(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
void *event_info)
{
Widget_Data *wd = elm_widget_data_get(data);
if (!wd) return;
_event_process(data, obj, event_info, EVAS_CALLBACK_MULTI_UP);
}
EAPI Eina_Bool
elm_gesture_layer_hold_events_get(const Evas_Object *obj)
{
ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return EINA_FALSE;
return !wd->repeat_events;
}
EAPI void
elm_gesture_layer_hold_events_set(Evas_Object *obj, Eina_Bool hold_events)
{
ELM_CHECK_WIDTYPE(obj, widtype);
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
wd->repeat_events = !(!!hold_events);
}
EAPI double
elm_gesture_layer_zoom_step_get(const Evas_Object *obj)
{
ELM_CHECK_WIDTYPE(obj, widtype) 0;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return 0;
return wd->zoom_step;
}
EAPI void
elm_gesture_layer_zoom_step_set(Evas_Object *obj, double step)
{
ELM_CHECK_WIDTYPE(obj, widtype);
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (step < 0) return;
wd->zoom_step = step;
}
EAPI double
elm_gesture_layer_rotate_step_get(const Evas_Object *obj)
{
ELM_CHECK_WIDTYPE(obj, widtype) 0;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return 0;
return wd->rotate_step;
}
EAPI void
elm_gesture_layer_rotate_step_set(Evas_Object *obj, double step)
{
ELM_CHECK_WIDTYPE(obj, widtype);
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return;
if (step < 0) return;
wd->rotate_step = step;
}
EAPI Eina_Bool
elm_gesture_layer_attach(Evas_Object *obj, Evas_Object *target)
{
ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
Widget_Data *wd = elm_widget_data_get(obj);
if (!wd) return EINA_FALSE;
if (!target) return EINA_FALSE;
/* if was attached before, unregister callbacks first */
if (wd->target)
_unregister_callbacks(obj);
wd->target = target;
_register_callbacks(obj);
return EINA_TRUE;
}
EAPI void
elm_gesture_layer_cb_set(Evas_Object *obj, Elm_Gesture_Type idx,
Elm_Gesture_State cb_type, Elm_Gesture_Event_Cb cb, void *data)
{
ELM_CHECK_WIDTYPE(obj, widtype);
Widget_Data *wd = elm_widget_data_get(obj);
Gesture_Info *p;
if (!wd) return;
if (!wd->gesture[idx])
wd->gesture[idx] = calloc(1, sizeof(Gesture_Info));
if (!wd->gesture[idx]) return;
p = wd->gesture[idx];
p->obj = obj;
p->g_type = idx;
p->fn[cb_type].cb = cb;
p->fn[cb_type].user_data = data;
p->state = ELM_GESTURE_STATE_UNDEFINED;
SET_TEST_BIT(p);
}
static void
_disable_hook(Evas_Object *obj)
{
if (elm_widget_disabled_get(obj))
_unregister_callbacks(obj);
else
_register_callbacks(obj);
}
EAPI Evas_Object *
elm_gesture_layer_add(Evas_Object *parent)
{
Evas_Object *obj;
Evas *e;
Widget_Data *wd;
ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL);
ELM_SET_WIDTYPE(widtype, "gesture_layer");
elm_widget_type_set(obj, "gesture_layer");
elm_widget_sub_object_add(parent, obj);
elm_widget_data_set(obj, wd);
elm_widget_del_hook_set(obj, _del_hook);
elm_widget_disable_hook_set(obj, _disable_hook);
wd->target = NULL;
wd->line_min_length =_elm_config->glayer_line_min_length * _elm_config->finger_size;
wd->zoom_distance_tolerance = _elm_config->glayer_zoom_distance_tolerance * _elm_config->finger_size;
wd->line_distance_tolerance = _elm_config->glayer_line_distance_tolerance * _elm_config->finger_size;
wd->zoom_finger_factor = _elm_config->glayer_zoom_finger_factor;
wd->zoom_wheel_factor = _elm_config->glayer_zoom_wheel_factor; /* mouse wheel zoom steps */
wd->rotate_angular_tolerance = _elm_config->glayer_rotate_angular_tolerance;
wd->line_angular_tolerance = _elm_config->glayer_line_angular_tolerance;
wd->flick_time_limit_ms = _elm_config->glayer_flick_time_limit_ms;
wd->long_tap_start_timeout = _elm_config->glayer_long_tap_start_timeout;
wd->repeat_events = EINA_TRUE;
wd->glayer_continues_enable = _elm_config->glayer_continues_enable;
#if defined(DEBUG_GESTURE_LAYER)
printf("size of Gestures = <%d>\n", sizeof(wd->gesture));
printf("initial values:\n\tzoom_finger_factor=<%f>\n\tzoom_distance_tolerance=<%d>\n\tline_min_length=<%d>\n\tline_distance_tolerance=<%d>\n\tzoom_wheel_factor=<%f>\n\trotate_angular_tolerance=<%f>\n\twd->line_angular_tolerance=<%f>\n\twd->flick_time_limit_ms=<%d>\n\twd->long_tap_start_timeout=<%f>\n\twd->zoom_step=<%f>\n\twd->rotate_step=<%f>\n\twd->glayer_continues_enable=<%d>\n ", wd->zoom_finger_factor, wd->zoom_distance_tolerance, wd->line_min_length, wd->line_distance_tolerance, wd->zoom_wheel_factor, wd->rotate_angular_tolerance, wd->line_angular_tolerance, wd->flick_time_limit_ms, wd->long_tap_start_timeout, wd->zoom_step, wd->rotate_step, wd->glayer_continues_enable);
#endif
memset(wd->gesture, 0, sizeof(wd->gesture));
return obj;
}