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',