From 7dd37558b1cd8c2c34b75a20a0bb194d95c6cfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Fri, 8 Apr 2022 19:40:27 +0200 Subject: [PATCH] clutter/gesture: Add a new ClutterAction for touch and mouse gestures Introduce ClutterGesture, a new ClutterAction subclass and the successor of ClutterGestureAction that brings the necessary tools to handle sequences of events and abstract touch and mouse gestures from those. The big difference compared to ClutterGestureAction is that ClutterGesture provides the implementation with point_added/moved/ended and sequences_cancelled events and expects the implementation to move the ClutterGesture through the ClutterGestureState state machine. This state machine is then used internally by ClutterGesture to coordinate with other gestures. With the next commits, ClutterGesture will handle relationships between conflicting gestures completely by itself, allowing the implementation or the user to specify the details of the relationship between two gestures. Part-of: --- clutter/clutter/clutter-enums.h | 11 + clutter/clutter/clutter-gesture.c | 1422 ++++++++++++++++++++++++++ clutter/clutter/clutter-gesture.h | 147 +++ clutter/clutter/clutter-marshal.list | 1 + clutter/clutter/clutter.h | 1 + clutter/clutter/meson.build | 2 + 6 files changed, 1584 insertions(+) create mode 100644 clutter/clutter/clutter-gesture.c create mode 100644 clutter/clutter/clutter-gesture.h diff --git a/clutter/clutter/clutter-enums.h b/clutter/clutter/clutter-enums.h index 608cb0fc7..45956bf57 100644 --- a/clutter/clutter/clutter-enums.h +++ b/clutter/clutter/clutter-enums.h @@ -1230,4 +1230,15 @@ typedef enum CLUTTER_GRAB_STATE_KEYBOARD), } ClutterGrabState; +typedef enum /*< prefix=CLUTTER_GESTURE_STATE >*/ +{ + CLUTTER_GESTURE_STATE_WAITING, + CLUTTER_GESTURE_STATE_POSSIBLE, + CLUTTER_GESTURE_STATE_RECOGNIZING, + CLUTTER_GESTURE_STATE_COMPLETED, + CLUTTER_GESTURE_STATE_CANCELLED, + + CLUTTER_N_GESTURE_STATES +} ClutterGestureState; + G_END_DECLS diff --git a/clutter/clutter/clutter-gesture.c b/clutter/clutter/clutter-gesture.c new file mode 100644 index 000000000..78bf85233 --- /dev/null +++ b/clutter/clutter/clutter-gesture.c @@ -0,0 +1,1422 @@ +/* + * Copyright (C) 2023 Jonas Dreßler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/** + * ClutterGesture: + * + * A #ClutterAction for recognizing gestures + * + * #ClutterGesture is a sub-class of #ClutterAction and an abstract base class + * for implementing the logic to recognize various input gestures. + * + * Implementing a #ClutterGesture is done by subclassing #ClutterGesture, + * connecting to the should_handle_sequence(), point_began()/moved()/ended() + * and sequences_cancelled() vfuncs, and then moving the gesture through the + * #ClutterGestureState state machine using clutter_gesture_set_state(). + * + * ## Recognizing new gestures + * + * #ClutterGesture uses five separate states to differentiate between the + * phases of gesture recognition. Those states also define whether to block or + * allow event delivery: + * + * - WAITING: The gesture will be starting out in this state if no points + * are available. When points are added, the state automatically moves + * to POSSIBLE before the point_began() vfunc gets called. + * + * - POSSIBLE: This is the state the gesture will be in when point_began() + * gets called the first time. As soon as the implementation is reasonably + * sure that the sequence of events is the gesture, it should set the state + * to RECOGNIZING. + * + * - RECOGNIZING: A continuous gesture is being recognized. In this state + * the implementation usually triggers UI changes as feedback to the user. + * + * - COMPLETED: The gesture was sucessfully recognized and has been completed. + * The gesture will automatically move to state WAITING after all the + * remaining points have ended. + * + * - CANCELLED: The gesture was either not started at all because preconditions + * were not fulfilled or it was cancelled by the implementation. + * The gesture will automatically move to state WAITING after all the + * remaining points have ended. + * + * Each #ClutterGesture starts out in the WAITING state and automatically + * moves to POSSIBLE when #ClutterGestureClass.should_handle_sequence() returns + * true for the first event of an event sequence. Events of this sequence must + * then be handled using the point_began(), point_moved(), point_ended() and + * sequences_cancelled() vfuncs. From these events, the implementation moves + * the gesture through the #ClutterGestureState state-machine. + * + * Note that point_ended() and sequences_cancelled() both have a default + * implementation which automatically moves the state of the gesture to + * CANCELLED. + * + * Note that it's not guaranteed that clutter_gesture_set_state() will always + * (and immediately) enter the requested state. To deal with this, never + * assume the state has changed after calling clutter_gesture_set_state(), + * and react to state changes by implementing the state_changed() vfunc. + */ + +#include "config.h" + +#include "clutter-gesture.h" + +#include "clutter-debug.h" +#include "clutter-enum-types.h" +#include "clutter-marshal.h" +#include "clutter-private.h" +#include "clutter-stage-private.h" + +#include + +static const char * state_to_string[] = { + "WAITING", + "POSSIBLE", + "RECOGNIZING", + "COMPLETED", + "CANCELLED", +}; +G_STATIC_ASSERT (sizeof (state_to_string) / sizeof (state_to_string[0]) == CLUTTER_N_GESTURE_STATES); + +typedef struct +{ + ClutterInputDevice *device; + ClutterEventSequence *sequence; + + ClutterEvent *begin_event; + ClutterEvent *previous_event; + ClutterEvent *latest_event; + + unsigned int n_buttons_pressed; + gboolean seen; + gboolean ended; +} GestureSequenceData; + +typedef struct _ClutterGesturePrivate ClutterGesturePrivate; + +struct _ClutterGesturePrivate +{ + GArray *sequences; + + unsigned int latest_index; + + ClutterGestureState state; +}; + +enum +{ + PROP_0, + + PROP_STATE, + + PROP_LAST +}; + +enum +{ + SHOULD_HANDLE_SEQUENCE, + MAY_RECOGNIZE, + RECOGNIZE, + END, + CANCEL, + + LAST_SIGNAL +}; + +static GParamSpec *obj_props[PROP_LAST]; +static guint obj_signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ClutterGesture, + clutter_gesture, + CLUTTER_TYPE_ACTION) + +static inline void +debug_message (ClutterGesture *self, + const char *format, + ...) G_GNUC_PRINTF (2, 3); + +static void +maybe_move_to_waiting (ClutterGesture *self); + +static void +set_state_authoritative (ClutterGesture *self, + ClutterGestureState new_state); + +inline void +debug_message (ClutterGesture *self, + const char *format, + ...) +{ + if (G_UNLIKELY (clutter_debug_flags & CLUTTER_DEBUG_GESTURES)) + { + va_list args; + char *str; + const char *name; + + va_start (args, format); + + str = g_strdup_vprintf (format, args); + name = clutter_actor_meta_get_name (CLUTTER_ACTOR_META (self)); + + CLUTTER_NOTE (GESTURES, + "<%s> [%p] %s", + name ? name : G_OBJECT_TYPE_NAME (self), + self, str); + + g_free (str); + va_end (args); + } +} + +static GestureSequenceData * +get_sequence_data (ClutterGesture *self, + ClutterInputDevice *device, + ClutterEventSequence *sequence, + unsigned int *index) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + unsigned int i; + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *iter = &g_array_index (priv->sequences, GestureSequenceData, i); + + if (!iter->ended && iter->device == device && iter->sequence == sequence) + { + if (index != NULL) + *index = i; + return iter; + } + } + + return NULL; +} + +static void +register_sequence (ClutterGesture *self, + const ClutterEvent *event) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + GestureSequenceData *seq_data; + ClutterInputDevice *device = clutter_event_get_device (event); + ClutterEventSequence *sequence = clutter_event_get_event_sequence (event); + + g_array_set_size (priv->sequences, priv->sequences->len + 1); + seq_data = &g_array_index (priv->sequences, GestureSequenceData, priv->sequences->len - 1); + + seq_data->device = device; + seq_data->sequence = sequence; + seq_data->n_buttons_pressed = 0; + seq_data->seen = FALSE; + seq_data->ended = FALSE; + seq_data->begin_event = clutter_event_copy (event); + + debug_message (self, + "[d=%p s=%p] Registered new sequence, n total sequences now: %u", + device, sequence, priv->sequences->len); +} + +static void +free_sequence_data (GestureSequenceData *seq_data) +{ + if (seq_data->latest_event) + clutter_event_free (seq_data->latest_event); + + if (seq_data->previous_event) + clutter_event_free (seq_data->previous_event); + + if (seq_data->begin_event) + clutter_event_free (seq_data->begin_event); +} + +static void +cancel_sequence (ClutterGesture *self, + unsigned int seq_index) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + ClutterGestureClass *gesture_class = CLUTTER_GESTURE_GET_CLASS (self); + GestureSequenceData *seq_data = + &g_array_index (priv->sequences, GestureSequenceData, seq_index); + + if (priv->state == CLUTTER_GESTURE_STATE_CANCELLED || + priv->state == CLUTTER_GESTURE_STATE_COMPLETED) + goto out; + + g_assert (priv->state == CLUTTER_GESTURE_STATE_POSSIBLE || + priv->state == CLUTTER_GESTURE_STATE_RECOGNIZING); + + /* If all sequences are cancelled, it's as if this sequence had never existed + * and therefore the gesture should never have moved into POSSIBLE. This + * means there's no reason to emit a sequences_cancelled() to the gesture + * implementation, and we can just cancel the gesture right away and move + * back into WAITING state. + * + * Note that this check is a bit sloppy and doesn't handle any sequences + * that ended or got cancelled before. In the case where sequences ended + * (as in: didn't get cancelled) before, we can not apply this shortcut and + * must leave the decision to the implementation. In the case where all + * sequences before were also cancelled, we should theoretically always + * cancel here too, but we're skipping that for simplicity reasons. + */ + if (priv->sequences->len == 1) + { + set_state_authoritative (self, CLUTTER_GESTURE_STATE_CANCELLED); + goto out; + } + + if (!seq_data->seen) + goto out; + + g_assert (!seq_data->ended); + + if (gesture_class->sequences_cancelled) + gesture_class->sequences_cancelled (self, &seq_index, 1); + +out: + seq_data->ended = TRUE; + + maybe_move_to_waiting (self); +} + +static void +cancel_point (ClutterGesture *self, + ClutterInputDevice *device, + ClutterEventSequence *sequence) +{ + unsigned int seq_index; + + if (!get_sequence_data (self, device, sequence, &seq_index)) + return; + + debug_message (self, "[d=%p s=%p] Cancelling point", device, sequence); + + cancel_sequence (self, seq_index); +} + +static void +cancel_all_points (ClutterGesture *self) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + g_autoptr (GArray) emission_points = NULL; + unsigned int i; + unsigned int n_ended; + ClutterGestureClass *gesture_class = CLUTTER_GESTURE_GET_CLASS (self); + + if (priv->state == CLUTTER_GESTURE_STATE_CANCELLED || + priv->state == CLUTTER_GESTURE_STATE_COMPLETED) + goto out; + + g_assert (priv->state == CLUTTER_GESTURE_STATE_POSSIBLE || + priv->state == CLUTTER_GESTURE_STATE_RECOGNIZING); + + emission_points = + g_array_sized_new (FALSE, TRUE, sizeof (unsigned int), priv->sequences->len); + + n_ended = 0; + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *seq_data = + &g_array_index (priv->sequences, GestureSequenceData, i); + + if (seq_data->ended) + n_ended++; + + if (seq_data->seen && !seq_data->ended) + g_array_append_val (emission_points, i); + } + + /* Just like in cancel_sequence(), force-cancel the gesture in case all sequences + * got cancelled, and none of them ended before. Also similar to cancel_sequence(), + * cheap out on the check a bit and ignore the case where sequences have + * already been cancelled before. + */ + if (n_ended == 0) + { + set_state_authoritative (self, CLUTTER_GESTURE_STATE_CANCELLED); + goto out; + } + + if (emission_points->len == 0) + goto out; + + if (gesture_class->sequences_cancelled) + { + gesture_class->sequences_cancelled (self, + (unsigned int *) emission_points->data, + emission_points->len); + } + +out: + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *seq_data = + &g_array_index (priv->sequences, GestureSequenceData, i); + + seq_data->ended = TRUE; + } + + maybe_move_to_waiting (self); +} + +static gboolean +gesture_may_start (ClutterGesture *self) +{ + gboolean may_recognize; + + g_signal_emit (self, obj_signals[MAY_RECOGNIZE], 0, &may_recognize); + if (!may_recognize) + { + debug_message (self, + "::may-recognize prevented gesture from recognizing"); + return FALSE; + } + + return TRUE; +} + +static void +set_state (ClutterGesture *self, + ClutterGestureState new_state) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + ClutterGestureState old_state; + ClutterGestureClass *gesture_class = CLUTTER_GESTURE_GET_CLASS (self); + + if (priv->state == new_state) + { + debug_message (self, "Skipping state change %s -> %s", + state_to_string[priv->state], state_to_string[new_state]); + return; + } + + switch (priv->state) + { + case CLUTTER_GESTURE_STATE_WAITING: + g_assert (new_state == CLUTTER_GESTURE_STATE_POSSIBLE); + break; + case CLUTTER_GESTURE_STATE_POSSIBLE: + g_assert (new_state == CLUTTER_GESTURE_STATE_RECOGNIZING || + new_state == CLUTTER_GESTURE_STATE_COMPLETED || + new_state == CLUTTER_GESTURE_STATE_CANCELLED); + break; + case CLUTTER_GESTURE_STATE_RECOGNIZING: + g_assert (new_state == CLUTTER_GESTURE_STATE_COMPLETED || + new_state == CLUTTER_GESTURE_STATE_CANCELLED); + break; + case CLUTTER_GESTURE_STATE_COMPLETED: + g_assert (new_state == CLUTTER_GESTURE_STATE_WAITING); + break; + case CLUTTER_GESTURE_STATE_CANCELLED: + g_assert (new_state == CLUTTER_GESTURE_STATE_WAITING); + break; + case CLUTTER_N_GESTURE_STATES: + g_assert_not_reached (); + break; + } + + if (priv->state == CLUTTER_GESTURE_STATE_POSSIBLE) + { + if (new_state == CLUTTER_GESTURE_STATE_RECOGNIZING || + new_state == CLUTTER_GESTURE_STATE_COMPLETED) + { + if (!gesture_may_start (self)) + { + set_state_authoritative (self, CLUTTER_GESTURE_STATE_CANCELLED); + return; + } + } + } + + old_state = priv->state; + + if (new_state == CLUTTER_GESTURE_STATE_RECOGNIZING || + (old_state != CLUTTER_GESTURE_STATE_RECOGNIZING && + new_state == CLUTTER_GESTURE_STATE_COMPLETED)) + { + ClutterActor *actor; + + actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self)); + if (actor) + { + ClutterStage *stage = CLUTTER_STAGE (clutter_actor_get_stage (actor)); + + if (stage) + { + unsigned int i; + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *seq_data = + &g_array_index (priv->sequences, GestureSequenceData, i); + + if (seq_data->ended) + continue; + + clutter_stage_notify_action_implicit_grab (stage, + seq_data->device, + seq_data->sequence); + } + } + } + } + + if (new_state == CLUTTER_GESTURE_STATE_WAITING) + g_array_set_size (priv->sequences, 0); + + priv->state = new_state; + + debug_message (self, "State change (%s -> %s)", + state_to_string[old_state], state_to_string[new_state]); + + if (new_state == CLUTTER_GESTURE_STATE_RECOGNIZING || + (old_state != CLUTTER_GESTURE_STATE_RECOGNIZING && + new_state == CLUTTER_GESTURE_STATE_COMPLETED)) + g_signal_emit (self, obj_signals[RECOGNIZE], 0); + + if (old_state == CLUTTER_GESTURE_STATE_RECOGNIZING && + new_state == CLUTTER_GESTURE_STATE_COMPLETED) + g_signal_emit (self, obj_signals[END], 0); + + if (old_state == CLUTTER_GESTURE_STATE_RECOGNIZING && + new_state == CLUTTER_GESTURE_STATE_CANCELLED) + g_signal_emit (self, obj_signals[CANCEL], 0); + + if (gesture_class->state_changed) + gesture_class->state_changed (self, old_state, new_state); + + g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_STATE]); +} + +void +maybe_move_to_waiting (ClutterGesture *self) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + unsigned int i; + + if (priv->state != CLUTTER_GESTURE_STATE_COMPLETED && + priv->state != CLUTTER_GESTURE_STATE_CANCELLED) + return; + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *seq_data = &g_array_index (priv->sequences, GestureSequenceData, i); + + if (!seq_data->ended) + return; + } + + set_state (self, CLUTTER_GESTURE_STATE_WAITING); +} + +void +set_state_authoritative (ClutterGesture *self, + ClutterGestureState new_state) +{ + set_state (self, new_state); + maybe_move_to_waiting (self); +} + +static gboolean +clutter_gesture_real_should_handle_sequence (ClutterGesture *self, + const ClutterEvent *sequence_begin_event) +{ + /* We expect the actual gesture implementation to implement + * should_handle_sequence() vfunc and to tell us whether it's able to + * handle this kind of event. + */ + g_warning ("gesture <%s> [<%s>:%p]: should_handle_sequence() not implemented", + clutter_actor_meta_get_name (CLUTTER_ACTOR_META (self)), + G_OBJECT_TYPE_NAME (self), self); + + return FALSE; +} + +static gboolean +clutter_gesture_real_may_recognize (ClutterGesture *self) +{ + return TRUE; +} + +static void +clutter_gesture_real_point_ended (ClutterGesture *self, + unsigned int point_index) +{ + /* As convenience for implementations, if this is the last point, move + * to CANCELLED. + */ + if (clutter_gesture_get_n_points (self) == 1) + set_state_authoritative (self, CLUTTER_GESTURE_STATE_CANCELLED); +} + +static void +clutter_gesture_real_sequences_cancelled (ClutterGesture *self, + unsigned int *sequences, + unsigned int n_sequences) +{ + if (clutter_gesture_get_n_points (self) == n_sequences) + set_state_authoritative (self, CLUTTER_GESTURE_STATE_CANCELLED); +} + +static void +handle_pointer_event (ClutterGesture *self, + unsigned int seq_index, + const ClutterEvent *event) +{ + ClutterGestureClass *gesture_class = CLUTTER_GESTURE_GET_CLASS (self); + + switch (clutter_event_type (event)) + { + case CLUTTER_BUTTON_PRESS: + case CLUTTER_TOUCH_BEGIN: + if (gesture_class->point_began) + gesture_class->point_began (self, seq_index); + break; + + case CLUTTER_MOTION: + case CLUTTER_TOUCH_UPDATE: + if (gesture_class->point_moved) + gesture_class->point_moved (self, seq_index); + break; + + case CLUTTER_BUTTON_RELEASE: + case CLUTTER_TOUCH_END: + if (gesture_class->point_ended) + gesture_class->point_ended (self, seq_index); + break; + + case CLUTTER_TOUCH_CANCEL: + cancel_sequence (self, seq_index); + break; + + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +is_sequence_end_event (const ClutterEvent *event) +{ + switch (clutter_event_type (event)) + { + case CLUTTER_BUTTON_PRESS: + case CLUTTER_MOTION: + case CLUTTER_TOUCH_BEGIN: + case CLUTTER_TOUCH_UPDATE: + return FALSE; + + case CLUTTER_BUTTON_RELEASE: + case CLUTTER_TOUCH_END: + case CLUTTER_TOUCH_CANCEL: + return TRUE; + + case CLUTTER_ENTER: + case CLUTTER_LEAVE: + return FALSE; + + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +clutter_gesture_handle_event (ClutterAction *action, + const ClutterEvent *event) +{ + ClutterGesture *self = CLUTTER_GESTURE (action); + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + ClutterGestureClass *gesture_class = CLUTTER_GESTURE_GET_CLASS (self); + ClutterInputDevice *device = clutter_event_get_device (event); + ClutterEventSequence *sequence = clutter_event_get_event_sequence (event); + ClutterEventType event_type = clutter_event_type (event); + GestureSequenceData *seq_data; + unsigned int seq_index; + gboolean is_first_event; + gboolean should_emit; + gboolean may_remove_point = TRUE; + ClutterGestureState old_state = priv->state; + + if (clutter_event_get_flags (event) & CLUTTER_EVENT_FLAG_SYNTHETIC) + return CLUTTER_EVENT_PROPAGATE; + + if (priv->state == CLUTTER_GESTURE_STATE_CANCELLED || + priv->state == CLUTTER_GESTURE_STATE_COMPLETED) + return CLUTTER_EVENT_PROPAGATE; + + if (event_type == CLUTTER_BUTTON_PRESS || + event_type == CLUTTER_TOUCH_BEGIN) + { + ClutterInputDevice *source_device = + clutter_event_get_source_device (event); + gboolean retval; + + if (priv->sequences->len > 0) + { + unsigned int i; + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *iter = &g_array_index (priv->sequences, GestureSequenceData, i); + ClutterInputDevice *iter_source_device; + + if (iter->ended) + continue; + + iter_source_device = clutter_event_get_source_device (iter->begin_event); + + if (iter_source_device != source_device) + return CLUTTER_EVENT_PROPAGATE; + + break; + } + } + + g_signal_emit (self, obj_signals[SHOULD_HANDLE_SEQUENCE], 0, + event, &retval); + if (!retval) + return CLUTTER_EVENT_PROPAGATE; + + if (priv->state == CLUTTER_GESTURE_STATE_WAITING) + { + set_state_authoritative (self, CLUTTER_GESTURE_STATE_POSSIBLE); + g_assert (priv->state == CLUTTER_GESTURE_STATE_POSSIBLE); + } + + register_sequence (self, event); + } + + if ((seq_data = get_sequence_data (self, device, sequence, &seq_index)) == NULL) + return CLUTTER_EVENT_PROPAGATE; + + if (event_type == CLUTTER_ENTER || event_type == CLUTTER_LEAVE) + { + if (gesture_class->crossing_event) + { + gesture_class->crossing_event (self, + seq_index, + event_type, + clutter_event_get_time (event), + clutter_event_get_flags (event), + clutter_event_get_source (event), + clutter_event_get_related (event)); + } + + return CLUTTER_EVENT_PROPAGATE; + } + + g_assert (priv->state != CLUTTER_GESTURE_STATE_WAITING); + + is_first_event = !seq_data->seen; + should_emit = + priv->state == CLUTTER_GESTURE_STATE_POSSIBLE || + priv->state == CLUTTER_GESTURE_STATE_RECOGNIZING; + + if (event_type == CLUTTER_BUTTON_PRESS) + { + seq_data->n_buttons_pressed++; + if (seq_data->n_buttons_pressed >= 2) + should_emit = FALSE; + } + else if (event_type == CLUTTER_BUTTON_RELEASE) + { + seq_data->n_buttons_pressed--; + if (seq_data->n_buttons_pressed >= 1) + may_remove_point = should_emit = FALSE; + } + + if (should_emit) + { + if (seq_data->previous_event) + clutter_event_free (seq_data->previous_event); + seq_data->previous_event = seq_data->latest_event; + seq_data->latest_event = clutter_event_copy (event); + + priv->latest_index = seq_index; + + seq_data->seen = TRUE; + + switch (event_type) + { + case CLUTTER_BUTTON_PRESS: + case CLUTTER_MOTION: + case CLUTTER_BUTTON_RELEASE: + case CLUTTER_TOUCH_BEGIN: + case CLUTTER_TOUCH_UPDATE: + case CLUTTER_TOUCH_END: + case CLUTTER_TOUCH_CANCEL: + handle_pointer_event (self, seq_index, event); + break; + + default: + g_assert_not_reached (); + break; + } + } + + if (may_remove_point && is_sequence_end_event (event)) + { + seq_data->ended = TRUE; + + maybe_move_to_waiting (self); + } + + /* If we were already RECOGNIZING, a new point was added and the gesture + * wasn't cancelled, we'll interpret this as a hint to claim the new + * point, too. + * + * Note that we check for !seq_data->ended here because the sequence might + * have been cancelled as an effect of the points_began() implementation, eg. + * in case the gesture implementation unmapped our actor. + */ + if (is_first_event && !seq_data->ended && + old_state == CLUTTER_GESTURE_STATE_RECOGNIZING && + priv->state == CLUTTER_GESTURE_STATE_RECOGNIZING) + { + ClutterActor *actor; + + actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self)); + if (actor) + { + ClutterStage *stage = CLUTTER_STAGE (clutter_actor_get_stage (actor)); + + if (stage) + clutter_stage_notify_action_implicit_grab (stage, device, sequence); + } + } + + return CLUTTER_EVENT_PROPAGATE; +} + +static void +clutter_gesture_sequence_cancelled (ClutterAction *action, + ClutterInputDevice *device, + ClutterEventSequence *sequence) +{ + cancel_point (CLUTTER_GESTURE (action), device, sequence); +} + +static void +clutter_gesture_set_actor (ClutterActorMeta *meta, + ClutterActor *actor) +{ + ClutterGesture *self = CLUTTER_GESTURE (meta); + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + + if (priv->sequences->len) + { + debug_message (self, + "Detaching from actor while gesture has points, cancelling " + "all points"); + + cancel_all_points (self); + } + + CLUTTER_ACTOR_META_CLASS (clutter_gesture_parent_class)->set_actor (meta, actor); +} + +static void +clutter_gesture_set_enabled (ClutterActorMeta *meta, + gboolean is_enabled) +{ + ClutterGesture *self = CLUTTER_GESTURE (meta); + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + + if (!is_enabled && priv->sequences->len) + { + debug_message (self, + "Disabling gesture while it has points, cancelling all points"); + + cancel_all_points (self); + } + + CLUTTER_ACTOR_META_CLASS (clutter_gesture_parent_class)->set_enabled (meta, is_enabled); +} + +static void +clutter_gesture_get_property (GObject *gobject, + unsigned int prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterGesture *self = CLUTTER_GESTURE (gobject); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_enum (value, clutter_gesture_get_state (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_gesture_finalize (GObject *gobject) +{ + ClutterGesture *self = CLUTTER_GESTURE (gobject); + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + + g_assert (priv->state != CLUTTER_GESTURE_STATE_COMPLETED && + priv->state != CLUTTER_GESTURE_STATE_CANCELLED); + + if (priv->state != CLUTTER_GESTURE_STATE_WAITING) + { + g_warning ("gesture <%s> [<%s>:%p]: Finalizing while in active state (%s), " + "implementation didn't move the gesture to an end state.", + clutter_actor_meta_get_name (CLUTTER_ACTOR_META (self)), + G_OBJECT_TYPE_NAME (self), self, + state_to_string[priv->state]); + } + + g_array_unref (priv->sequences); + + G_OBJECT_CLASS (clutter_gesture_parent_class)->finalize (gobject); +} + +static void +clutter_gesture_class_init (ClutterGestureClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + ClutterActionClass *action_class = CLUTTER_ACTION_CLASS (klass); + + klass->should_handle_sequence = clutter_gesture_real_should_handle_sequence; + klass->may_recognize = clutter_gesture_real_may_recognize; + klass->point_ended = clutter_gesture_real_point_ended; + klass->sequences_cancelled = clutter_gesture_real_sequences_cancelled; + + action_class->handle_event = clutter_gesture_handle_event; + action_class->sequence_cancelled = clutter_gesture_sequence_cancelled; + + meta_class->set_actor = clutter_gesture_set_actor; + meta_class->set_enabled = clutter_gesture_set_enabled; + + gobject_class->get_property = clutter_gesture_get_property; + gobject_class->finalize = clutter_gesture_finalize; + + /** + * ClutterGesture:state: + * + * The current state of the gesture. + */ + obj_props[PROP_STATE] = + g_param_spec_enum ("state", + "state", + "state", + CLUTTER_TYPE_GESTURE_STATE, + CLUTTER_GESTURE_STATE_WAITING, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + obj_props); + + /** + * ClutterGesture::should-handle-sequence: + * @gesture: the #ClutterGesture that emitted the signal + * @sequence_begin_event: the #ClutterEvent beginning the sequence + * + * The ::should-handle-sequence signal is emitted when a sequence gets added + * to the gesture. Return %FALSE to make the gesture ignore the sequence of + * events. + * + * Returns: %TRUE if the gesture may handle the sequence, %FALSE if it may not. + */ + obj_signals[SHOULD_HANDLE_SEQUENCE] = + g_signal_new (I_("should-handle-sequence"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ClutterGestureClass, should_handle_sequence), + _clutter_boolean_continue_accumulator, + NULL, _clutter_marshal_BOOLEAN__BOXED, + G_TYPE_BOOLEAN, 1, + CLUTTER_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + g_signal_set_va_marshaller (obj_signals[SHOULD_HANDLE_SEQUENCE], + G_TYPE_FROM_CLASS (gobject_class), + _clutter_marshal_BOOLEAN__BOXEDv); + + /** + * ClutterGesture::may-recognize: + * @gesture: the #ClutterGesture that emitted the signal + * + * The ::may-recognize signal is emitted if the gesture might become + * active and move to RECOGNIZING. Its purpose is to allow the + * implementation or a user of a gesture to prohibit the gesture + * from starting when needed. + * + * Returns: %TRUE if the gesture may recognize, %FALSE if it may not. + */ + obj_signals[MAY_RECOGNIZE] = + g_signal_new (I_("may-recognize"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterGestureClass, may_recognize), + _clutter_boolean_continue_accumulator, + NULL, NULL, + G_TYPE_BOOLEAN, 0, + G_TYPE_NONE); + + /** + * ClutterGesture::recognize: + * @gesture: the #ClutterGesture that emitted the signal + * + * The ::recognize signal is emitted when the gesture recognizes. + * + * This is the signal gesture users are supposed to use for implementing + * actions on gesture recognize. + */ + obj_signals[RECOGNIZE] = + g_signal_new (I_("recognize"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + g_signal_set_va_marshaller (obj_signals[RECOGNIZE], + G_TYPE_FROM_CLASS (gobject_class), + _clutter_marshal_VOID__VOIDv); + + /** + * ClutterGesture::end: + * @gesture: the #ClutterGesture that emitted the signal + * + * The ::end signal is emitted when a continuous gesture ends. + */ + obj_signals[END] = + g_signal_new (I_("end"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + g_signal_set_va_marshaller (obj_signals[END], + G_TYPE_FROM_CLASS (gobject_class), + _clutter_marshal_VOID__VOIDv); + + /** + * ClutterGesture::cancel: + * @gesture: the #ClutterGesture that emitted the signal + * + * The ::cancel signal is emitted when a continuous gesture got cancelled. + */ + obj_signals[CANCEL] = + g_signal_new (I_("cancel"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + g_signal_set_va_marshaller (obj_signals[CANCEL], + G_TYPE_FROM_CLASS (gobject_class), + _clutter_marshal_VOID__VOIDv); +} + +static void +clutter_gesture_init (ClutterGesture *self) +{ + ClutterGesturePrivate *priv = clutter_gesture_get_instance_private (self); + + priv->sequences = g_array_sized_new (FALSE, TRUE, sizeof (GestureSequenceData), 3); + g_array_set_clear_func (priv->sequences, (GDestroyNotify) free_sequence_data); + + priv->latest_index = 0; + + priv->state = CLUTTER_GESTURE_STATE_WAITING; + +} + +/** + * clutter_gesture_set_state: (skip) + * + * Sets the state of the gesture. This method is private to gesture + * implementations. + * + * Allowed state transitions are: + * + * - From POSSIBLE into RECOGNIZING, COMPLETED or CANCELLED. + * - From RECOGNIZING into COMPLETED or CANCELLED. + */ +void +clutter_gesture_set_state (ClutterGesture *self, + ClutterGestureState state) +{ + ClutterGesturePrivate *priv; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + + priv = clutter_gesture_get_instance_private (self); + + debug_message (self, "State change requested: %s -> %s", + state_to_string[priv->state], state_to_string[state]); + + if ((priv->state == CLUTTER_GESTURE_STATE_POSSIBLE && + (state == CLUTTER_GESTURE_STATE_RECOGNIZING || + state == CLUTTER_GESTURE_STATE_COMPLETED || + state == CLUTTER_GESTURE_STATE_CANCELLED)) || + (priv->state == CLUTTER_GESTURE_STATE_RECOGNIZING && + (state == CLUTTER_GESTURE_STATE_COMPLETED || + state == CLUTTER_GESTURE_STATE_CANCELLED))) + { + set_state_authoritative (self, state); + } + else + { + /* For sake of simplicity, never complain about unnecessary tries to cancel */ + if (state == CLUTTER_GESTURE_STATE_CANCELLED) + return; + + g_warning ("gesture <%s> [<%s>:%p]: Requested invalid state change: %s -> %s", + clutter_actor_meta_get_name (CLUTTER_ACTOR_META (self)), + G_OBJECT_TYPE_NAME (self), self, + state_to_string[priv->state], state_to_string[state]); + } +} + +/** + * clutter_gesture_cancel: + * @self: a #ClutterGesture + * + * Cancels the gesture by setting its state to CANCELLED. + */ +void +clutter_gesture_cancel (ClutterGesture *self) +{ + clutter_gesture_set_state (self, CLUTTER_GESTURE_STATE_CANCELLED); +} + +/** + * clutter_gesture_reset_state_machine: (skip) + */ +void +clutter_gesture_reset_state_machine (ClutterGesture *self) +{ + ClutterGesturePrivate *priv; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + + priv = clutter_gesture_get_instance_private (self); + + if (priv->state == CLUTTER_GESTURE_STATE_CANCELLED || + priv->state == CLUTTER_GESTURE_STATE_COMPLETED) + set_state_authoritative (self, CLUTTER_GESTURE_STATE_WAITING); +} + +/** + * clutter_gesture_get_state: + * @self: a #ClutterGesture + * + * Gets the current state of the gesture. + * + * Returns: the #ClutterGestureState + */ +ClutterGestureState +clutter_gesture_get_state (ClutterGesture *self) +{ + ClutterGesturePrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_GESTURE (self), CLUTTER_GESTURE_STATE_WAITING); + + priv = clutter_gesture_get_instance_private (self); + + return priv->state; +} + +/** + * clutter_gesture_get_n_points: + * @self: a #ClutterGesture + * + * Retrieves the number of active points the gesture currently has. + * + * Returns: the number of active points + */ +unsigned int +clutter_gesture_get_n_points (ClutterGesture *self) +{ + ClutterGesturePrivate *priv; + unsigned int i, n_points = 0; + + g_return_val_if_fail (CLUTTER_IS_GESTURE (self), 0); + + priv = clutter_gesture_get_instance_private (self); + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *seq_data = &g_array_index (priv->sequences, GestureSequenceData, i); + + if (seq_data->seen && !seq_data->ended) + n_points++; + } + + return n_points; +} + +/** + * clutter_gesture_get_points: + * @self: a #ClutterGesture + * @n_points: (out) (optional): number of points + * + * Retrieves an array of the currently active points of the gesture, the array is + * ordered in the order the points were added in (newest to oldest). + * + * Returns: (array length=n_points): array with active points of the gesture + */ +unsigned int * +clutter_gesture_get_points (ClutterGesture *self, + size_t *n_points) +{ + ClutterGesturePrivate *priv; + GArray *points = NULL; + unsigned int i; + + g_return_val_if_fail (CLUTTER_IS_GESTURE (self), 0); + + priv = clutter_gesture_get_instance_private (self); + + points = g_array_sized_new (TRUE, TRUE, sizeof (unsigned int), 1); + + for (i = 0; i < priv->sequences->len; i++) + { + GestureSequenceData *seq_data = + &g_array_index (priv->sequences, GestureSequenceData, i); + + if (seq_data->seen && !seq_data->ended) + g_array_append_val (points, i); + } + + return (unsigned int *) g_array_steal (points, n_points); +} + +/** + * clutter_gesture_get_point_coords: + * @self: a #ClutterGesture + * @point_index: index of the point + * @coords_out: (out): a #graphene_point_t + * + * Retrieves the latest coordinates of the point with index @point_index. + */ +void +clutter_gesture_get_point_coords (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + ClutterActor *action_actor; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + g_return_if_fail (coords_out != NULL); + + priv = clutter_gesture_get_instance_private (self); + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index == -1 ? priv->latest_index : point_index); + + clutter_event_get_position (seq_data->latest_event, coords_out); + + action_actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self)); + if (action_actor) + { + clutter_actor_transform_stage_point (action_actor, + coords_out->x, coords_out->y, + &coords_out->x, &coords_out->y); + } +} + +/** + * clutter_gesture_get_point_coords_abs: (skip) + */ +void +clutter_gesture_get_point_coords_abs (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + g_return_if_fail (coords_out != NULL); + + priv = clutter_gesture_get_instance_private (self); + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index == -1 ? priv->latest_index : point_index); + + clutter_event_get_position (seq_data->latest_event, coords_out); +} + +/** + * clutter_gesture_get_point_begin_coords: + * @self: a #ClutterGesture + * @point_index: index of the point + * @coords_out: (out): a #graphene_point_t + * + * Retrieves the begin coordinates of the point with index @point_index. + */ +void +clutter_gesture_get_point_begin_coords (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + ClutterActor *action_actor; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + g_return_if_fail (coords_out != NULL); + + priv = clutter_gesture_get_instance_private (self); + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index == -1 ? priv->latest_index : point_index); + + clutter_event_get_position (seq_data->begin_event, coords_out); + + action_actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self)); + if (action_actor) + { + clutter_actor_transform_stage_point (action_actor, + coords_out->x, coords_out->y, + &coords_out->x, &coords_out->y); + } +} + +/** + * clutter_gesture_get_point_begin_coords_abs: (skip) + */ +void +clutter_gesture_get_point_begin_coords_abs (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + g_return_if_fail (coords_out != NULL); + + priv = clutter_gesture_get_instance_private (self); + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index == -1 ? priv->latest_index : point_index); + + clutter_event_get_position (seq_data->begin_event, coords_out); +} + +/** + * clutter_gesture_get_point_previous_coords: + * @self: a #ClutterGesture + * @point_index: index of the point + * @coords_out: (out): a #graphene_point_t + * + * Retrieves the previous coordinates of the point with index @point_index. + */ +void +clutter_gesture_get_point_previous_coords (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + ClutterActor *action_actor; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + g_return_if_fail (coords_out != NULL); + + priv = clutter_gesture_get_instance_private (self); + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index == -1 ? priv->latest_index : point_index); + + clutter_event_get_position (seq_data->previous_event, coords_out); + + action_actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self)); + if (action_actor) + { + clutter_actor_transform_stage_point (action_actor, + coords_out->x, coords_out->y, + &coords_out->x, &coords_out->y); + } +} + +/** + * clutter_gesture_get_point_previous_coords_abs: (skip) + */ +void +clutter_gesture_get_point_previous_coords_abs (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + + g_return_if_fail (CLUTTER_IS_GESTURE (self)); + g_return_if_fail (coords_out != NULL); + + priv = clutter_gesture_get_instance_private (self); + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index == -1 ? priv->latest_index : point_index); + + clutter_event_get_position (seq_data->previous_event, coords_out); +} + +/** + * clutter_gesture_get_point_event: + * @self: a #ClutterGesture + * @point_index: index of the point + * + * Retrieves the the latest event of the point with index @point_index. + * + * Returns: The #ClutterEvent + */ +const ClutterEvent * +clutter_gesture_get_point_event (ClutterGesture *self, + int point_index) +{ + ClutterGesturePrivate *priv; + GestureSequenceData *seq_data; + + g_return_val_if_fail (CLUTTER_IS_GESTURE (self), NULL); + + priv = clutter_gesture_get_instance_private (self); + + g_return_val_if_fail (point_index < (int) priv->sequences->len, NULL); + g_return_val_if_fail (priv->latest_index < priv->sequences->len, NULL); + + seq_data = + &g_array_index (priv->sequences, GestureSequenceData, + point_index < 0 ? priv->latest_index : point_index); + + return seq_data->latest_event; +} diff --git a/clutter/clutter/clutter-gesture.h b/clutter/clutter/clutter-gesture.h new file mode 100644 index 000000000..d11f9a5a1 --- /dev/null +++ b/clutter/clutter/clutter-gesture.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 Jonas Dreßler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_GESTURE (clutter_gesture_get_type ()) + +CLUTTER_EXPORT +G_DECLARE_DERIVABLE_TYPE (ClutterGesture, clutter_gesture, + CLUTTER, GESTURE, ClutterAction) + +struct _ClutterGestureClass +{ + ClutterActionClass parent_class; + + /** + * ClutterGestureClass::should_handle_sequence: (skip) + */ + gboolean (* should_handle_sequence) (ClutterGesture *self, + const ClutterEvent *sequence_begin_event); + + /** + * ClutterGestureClass::point_began: (skip) + */ + void (* point_began) (ClutterGesture *self, + unsigned int sequence_index); + + /** + * ClutterGestureClass::point_moved: (skip) + */ + void (* point_moved) (ClutterGesture *self, + unsigned int sequence_index); + + /** + * ClutterGestureClass::point_ended: (skip) + */ + void (* point_ended) (ClutterGesture *self, + unsigned int sequence_index); + + /** + * ClutterGestureClass::sequences_cancelled: (skip) + */ + void (* sequences_cancelled) (ClutterGesture *self, + unsigned int *sequences, + unsigned int n_sequences); + + /** + * ClutterGestureClass::state_changed: (skip) + */ + void (* state_changed) (ClutterGesture *self, + ClutterGestureState old_state, + ClutterGestureState new_state); + + /** + * ClutterGestureClass::crossing_event: (skip) + */ + void (* crossing_event) (ClutterGesture *self, + unsigned int sequence_index, + ClutterEventType type, + uint32_t time, + ClutterEventFlags flags, + ClutterActor *source_actor, + ClutterActor *related_actor); + + /** + * ClutterGestureClass::may_recognize: (skip) + */ + gboolean (* may_recognize) (ClutterGesture *self); +}; + +CLUTTER_EXPORT +void clutter_gesture_set_state (ClutterGesture *self, + ClutterGestureState state); + +CLUTTER_EXPORT +ClutterGestureState clutter_gesture_get_state (ClutterGesture *self); + +CLUTTER_EXPORT +void clutter_gesture_cancel (ClutterGesture *self); + +CLUTTER_EXPORT +void clutter_gesture_reset_state_machine (ClutterGesture *self); + +CLUTTER_EXPORT +unsigned int clutter_gesture_get_n_points (ClutterGesture *self); + +CLUTTER_EXPORT +unsigned int * clutter_gesture_get_points (ClutterGesture *self, + size_t *n_points); + +CLUTTER_EXPORT +void clutter_gesture_get_point_coords (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out); + +CLUTTER_EXPORT +void clutter_gesture_get_point_coords_abs (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out); + +CLUTTER_EXPORT +void clutter_gesture_get_point_begin_coords (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out); + +CLUTTER_EXPORT +void clutter_gesture_get_point_begin_coords_abs (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out); + +CLUTTER_EXPORT +void clutter_gesture_get_point_previous_coords (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out); + +CLUTTER_EXPORT +void clutter_gesture_get_point_previous_coords_abs (ClutterGesture *self, + int point_index, + graphene_point_t *coords_out); + +CLUTTER_EXPORT +const ClutterEvent * clutter_gesture_get_point_event (ClutterGesture *self, + int point_index); + +G_END_DECLS diff --git a/clutter/clutter/clutter-marshal.list b/clutter/clutter/clutter-marshal.list index 8fa9ccf91..c983888c3 100644 --- a/clutter/clutter/clutter-marshal.list +++ b/clutter/clutter/clutter-marshal.list @@ -35,3 +35,4 @@ VOID:STRING,INT VOID:UINT,STRING,UINT VOID:UINT,UINT VOID:STRING,INT,POINTER +VOID:VOID diff --git a/clutter/clutter/clutter.h b/clutter/clutter/clutter.h index a1de321aa..69cc98670 100644 --- a/clutter/clutter/clutter.h +++ b/clutter/clutter/clutter.h @@ -59,6 +59,7 @@ #include "clutter/clutter-frame-clock.h" #include "clutter/clutter-frame.h" #include "clutter/clutter-gesture-action.h" +#include "clutter/clutter-gesture.h" #include "clutter/clutter-grab.h" #include "clutter/clutter-grid-layout.h" #include "clutter/clutter-image.h" diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build index dd139c8c0..11b5a1936 100644 --- a/clutter/clutter/meson.build +++ b/clutter/clutter/meson.build @@ -33,6 +33,7 @@ clutter_headers = [ 'clutter-frame-clock.h', 'clutter-frame.h', 'clutter-gesture-action.h', + 'clutter-gesture.h', 'clutter-grab.h', 'clutter-grid-layout.h', 'clutter-image.h', @@ -117,6 +118,7 @@ clutter_sources = [ 'clutter-frame-clock.c', 'clutter-frame.c', 'clutter-gesture-action.c', + 'clutter-gesture.c', 'clutter-grab.c', 'clutter-graphene.c', 'clutter-grid-layout.c',