diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 071b786c9..2f158a83e 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -85,6 +85,7 @@ source_h = \ $(srcdir)/clutter-desaturate-effect.h \ $(srcdir)/clutter-device-manager.h \ $(srcdir)/clutter-drag-action.h \ + $(srcdir)/clutter-drop-action.h \ $(srcdir)/clutter-effect.h \ $(srcdir)/clutter-event.h \ $(srcdir)/clutter-feature.h \ @@ -169,6 +170,7 @@ source_c = \ $(srcdir)/clutter-desaturate-effect.c \ $(srcdir)/clutter-device-manager.c \ $(srcdir)/clutter-drag-action.c \ + $(srcdir)/clutter-drop-action.c \ $(srcdir)/clutter-effect.c \ $(srcdir)/clutter-event.c \ $(srcdir)/clutter-feature.c \ diff --git a/clutter/clutter-drop-action.c b/clutter/clutter-drop-action.c new file mode 100644 index 000000000..1052dc792 --- /dev/null +++ b/clutter/clutter-drop-action.c @@ -0,0 +1,358 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-drop-action.h" + +#include "clutter-actor-meta-private.h" +#include "clutter-actor-private.h" +#include "clutter-drag-action.h" +#include "clutter-main.h" +#include "clutter-marshal.h" +#include "clutter-stage-private.h" + +struct _ClutterDropActionPrivate +{ + ClutterActor *actor; + ClutterActor *stage; + + gulong mapped_id; + + guint is_inside : 1; +}; + +typedef struct _DropTarget { + ClutterActor *stage; + + gulong capture_id; + + GHashTable *actions; + + ClutterDropAction *last_action; +} DropTarget; + +enum +{ + CAN_DROP, + OVER_IN, + OVER_OUT, + DROP, + + LAST_SIGNAL +}; + +static guint drop_signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (ClutterDropAction, clutter_drop_action, CLUTTER_TYPE_ACTION) + +static void +drop_target_free (gpointer _data) +{ + DropTarget *data = _data; + + g_signal_handler_disconnect (data->stage, data->capture_id); + g_hash_table_destroy (data->actions); + g_free (data); +} + +static gboolean +on_stage_capture (ClutterStage *stage, + ClutterEvent *event, + gpointer user_data) +{ + DropTarget *data = user_data; + gfloat event_x, event_y; + ClutterInputDevice *device; + ClutterActor *actor, *drag_actor; + ClutterDropAction *drop_action; + gboolean was_reactive; + + if (!(clutter_event_type (event) == CLUTTER_MOTION || + clutter_event_type (event) == CLUTTER_BUTTON_RELEASE)) + return FALSE; + + if (!(clutter_event_get_state (event) & CLUTTER_BUTTON1_MASK)) + return FALSE; + + clutter_event_get_coords (event, &event_x, &event_y); + device = clutter_event_get_device (event); + + drag_actor = _clutter_stage_get_drag_actor (stage, device); + if (drag_actor == NULL) + return FALSE; + + /* get the actor under the cursor, excluding the dragged actor; we + * use reactivity because it won't cause any scene invalidation + */ + was_reactive = clutter_actor_get_reactive (drag_actor); + clutter_actor_set_reactive (drag_actor, FALSE); + + actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_REACTIVE, + event_x, + event_y); + if (actor == NULL || actor == CLUTTER_ACTOR (stage)) + { + if (data->last_action != NULL) + { + ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action); + + g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0, + clutter_actor_meta_get_actor (meta)); + + data->last_action = NULL; + } + + goto out; + } + + drop_action = g_hash_table_lookup (data->actions, actor); + + if (drop_action == NULL) + { + if (data->last_action != NULL) + { + ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action); + + g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0, + clutter_actor_meta_get_actor (meta)); + + data->last_action = NULL; + } + + goto out; + } + else + { + if (data->last_action != drop_action) + { + ClutterActorMeta *meta; + + if (data->last_action != NULL) + { + meta = CLUTTER_ACTOR_META (data->last_action); + + g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0, + clutter_actor_meta_get_actor (meta)); + } + + meta = CLUTTER_ACTOR_META (drop_action); + + g_signal_emit (drop_action, drop_signals[OVER_IN], 0, + clutter_actor_meta_get_actor (meta)); + } + + data->last_action = drop_action; + } + +out: + if (clutter_event_type (event) == CLUTTER_BUTTON_RELEASE) + { + if (data->last_action != NULL) + { + ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action); + gboolean can_drop = FALSE; + + g_signal_emit (data->last_action, drop_signals[CAN_DROP], 0, + clutter_actor_meta_get_actor (meta), + event_x, event_y, + &can_drop); + + if (can_drop) + { + g_signal_emit (data->last_action, drop_signals[DROP], 0, + clutter_actor_meta_get_actor (meta), + event_x, event_y); + } + } + + data->last_action = NULL; + } + + if (drag_actor != NULL) + clutter_actor_set_reactive (drag_actor, was_reactive); + + return FALSE; +} + +static void +drop_action_register (ClutterDropAction *self) +{ + ClutterDropActionPrivate *priv = self->priv; + DropTarget *data; + + g_assert (priv->stage != NULL); + + data = g_object_get_data (G_OBJECT (priv->stage), "__clutter_drop_targets"); + if (data == NULL) + { + data = g_new0 (DropTarget, 1); + + data->stage = priv->stage; + data->actions = g_hash_table_new (NULL, NULL); + data->capture_id = g_signal_connect (priv->stage, "captured-event", + G_CALLBACK (on_stage_capture), + data); + g_object_set_data_full (G_OBJECT (priv->stage), "__clutter_drop_targets", + data, + drop_target_free); + } + + g_hash_table_replace (data->actions, priv->actor, self); +} + +static void +drop_action_unregister (ClutterDropAction *self) +{ + ClutterDropActionPrivate *priv = self->priv; + DropTarget *data; + + data = g_object_get_data (G_OBJECT (priv->stage), "__clutter_drop_targets"); + if (data == NULL) + return; + + g_hash_table_remove (data->actions, priv->actor); + if (g_hash_table_size (data->actions) == 0) + g_object_set_data (G_OBJECT (data->stage), "__clutter_drop_targets", NULL); +} + +static void +on_actor_mapped (ClutterActor *actor, + GParamSpec *pspec, + ClutterDropAction *self) +{ + if (CLUTTER_ACTOR_IS_MAPPED (actor)) + { + if (self->priv->stage == NULL) + self->priv->stage = clutter_actor_get_stage (actor); + + drop_action_register (self); + } + else + drop_action_unregister (self); +} + +static void +clutter_drop_action_set_actor (ClutterActorMeta *meta, + ClutterActor *actor) +{ + ClutterDropActionPrivate *priv = CLUTTER_DROP_ACTION (meta)->priv; + + if (priv->actor != NULL) + { + drop_action_unregister (CLUTTER_DROP_ACTION (meta)); + + if (priv->mapped_id != 0) + g_signal_handler_disconnect (priv->actor, priv->mapped_id); + + priv->stage = NULL; + priv->actor = NULL; + priv->mapped_id = 0; + } + + priv->actor = actor; + + if (priv->actor != NULL) + { + priv->stage = clutter_actor_get_stage (actor); + priv->mapped_id = g_signal_connect (actor, "notify::mapped", + G_CALLBACK (on_actor_mapped), + meta); + + if (priv->stage != NULL) + drop_action_register (CLUTTER_DROP_ACTION (meta)); + } + + CLUTTER_ACTOR_META_CLASS (clutter_drop_action_parent_class)->set_actor (meta, actor); +} + +static gboolean +signal_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer user_data) +{ + gboolean continue_emission; + + continue_emission = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, continue_emission); + + return continue_emission; +} + +static gboolean +clutter_drop_action_real_can_drop (ClutterDropAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y) +{ + return TRUE; +} + +static void +clutter_drop_action_class_init (ClutterDropActionClass *klass) +{ + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + + g_type_class_add_private (klass, sizeof (ClutterDropActionPrivate)); + + meta_class->set_actor = clutter_drop_action_set_actor; + + klass->can_drop = clutter_drop_action_real_can_drop; + + drop_signals[CAN_DROP] = + g_signal_new (I_("can-drop"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterDropActionClass, can_drop), + signal_accumulator, NULL, + _clutter_marshal_BOOLEAN__OBJECT_FLOAT_FLOAT, + G_TYPE_BOOLEAN, 3, + CLUTTER_TYPE_ACTOR, + G_TYPE_FLOAT, + G_TYPE_FLOAT); + + drop_signals[OVER_IN] = + g_signal_new (I_("over-in"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterDropActionClass, over_in), + NULL, NULL, + _clutter_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + CLUTTER_TYPE_ACTOR); + + drop_signals[OVER_OUT] = + g_signal_new (I_("over-out"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterDropActionClass, over_out), + NULL, NULL, + _clutter_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + CLUTTER_TYPE_ACTOR); + + drop_signals[DROP] = + g_signal_new (I_("drop"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterDropActionClass, drop), + NULL, NULL, + _clutter_marshal_VOID__OBJECT_FLOAT_FLOAT, + G_TYPE_NONE, 3, + CLUTTER_TYPE_ACTOR, + G_TYPE_FLOAT, + G_TYPE_FLOAT); +} + +static void +clutter_drop_action_init (ClutterDropAction *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DROP_ACTION, + ClutterDropActionPrivate); +} + +ClutterAction * +clutter_drop_action_new (void) +{ + return g_object_new (CLUTTER_TYPE_DROP_ACTION, NULL); +} diff --git a/clutter/clutter-drop-action.h b/clutter/clutter-drop-action.h new file mode 100644 index 000000000..290c90eb6 --- /dev/null +++ b/clutter/clutter-drop-action.h @@ -0,0 +1,85 @@ +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be directly included." +#endif + +#ifndef __CLUTTER_DROP_ACTION_H__ +#define __CLUTTER_DROP_ACTION_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_DROP_ACTION (clutter_drop_action_get_type ()) +#define CLUTTER_DROP_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_DROP_ACTION, ClutterDropAction)) +#define CLUTTER_IS_DROP_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_DROP_ACTION)) +#define CLUTTER_DROP_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_DROP_ACTION, ClutterDropActionClass)) +#define CLUTTER_IS_DROP_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_DROP_ACTION)) +#define CLUTTER_DROP_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_DROP_ACTION, ClutterDropActionClass)) + +typedef struct _ClutterDropAction ClutterDropAction; +typedef struct _ClutterDropActionPrivate ClutterDropActionPrivate; +typedef struct _ClutterDropActionClass ClutterDropActionClass; + +/** + * ClutterDropAction: + * + * The ClutterDropAction structure contains only + * private data and should be accessed using the provided API. + * + * Since: 1.8 + */ +struct _ClutterDropAction +{ + /*< private >*/ + ClutterAction parent_instance; + + ClutterDropActionPrivate *priv; +}; + +/** + * ClutterDropActionClass: + * + * The ClutterDropActionClass structure contains + * only private data. + * + * Since: 1.8 + */ +struct _ClutterDropActionClass +{ + /*< private >*/ + ClutterActionClass parent_class; + + /*< public >*/ + gboolean (* can_drop) (ClutterDropAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y); + + void (* over_in) (ClutterDropAction *action, + ClutterActor *actor); + void (* over_out) (ClutterDropAction *action, + ClutterActor *actor); + + void (* drop) (ClutterDropAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y); + + /*< private >*/ + void (*_clutter_drop_action1) (void); + void (*_clutter_drop_action2) (void); + void (*_clutter_drop_action3) (void); + void (*_clutter_drop_action4) (void); + void (*_clutter_drop_action5) (void); + void (*_clutter_drop_action6) (void); + void (*_clutter_drop_action7) (void); + void (*_clutter_drop_action8) (void); +}; + +GType clutter_drop_action_get_type (void) G_GNUC_CONST; + +ClutterAction * clutter_drop_action_new (void); + +G_END_DECLS + +#endif /* __CLUTTER_DROP_ACTION_H__ */ diff --git a/clutter/clutter-marshal.list b/clutter/clutter-marshal.list index 9d433c68c..d189a82f6 100644 --- a/clutter/clutter-marshal.list +++ b/clutter/clutter-marshal.list @@ -2,6 +2,7 @@ BOOLEAN:BOXED BOOLEAN:OBJECT,ENUM BOOLEAN:STRING,UINT,FLAGS BOOLEAN:OBJECT +BOOLEAN:OBJECT,FLOAT,FLOAT BOXED:UINT,UINT DOUBLE:VOID UINT:VOID diff --git a/clutter/clutter.h b/clutter/clutter.h index d64a9b722..8753466bf 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -67,6 +67,7 @@ #include "clutter-desaturate-effect.h" #include "clutter-device-manager.h" #include "clutter-drag-action.h" +#include "clutter-drop-action.h" #include "clutter-effect.h" #include "clutter-event.h" #include "clutter-feature.h" diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index 7ed17e539..7935397d4 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -57,7 +57,8 @@ UNIT_TESTS = \ test-table-layout.c \ test-path-constraint.c \ test-snap-constraint.c \ - test-state-script.c + test-state-script.c \ + test-drop.c if X11_TESTS UNIT_TESTS += test-pixmap.c test-devices.c diff --git a/tests/interactive/test-drop.c b/tests/interactive/test-drop.c new file mode 100644 index 000000000..2b511d4e8 --- /dev/null +++ b/tests/interactive/test-drop.c @@ -0,0 +1,245 @@ +#include +#include + +#define TARGET_SIZE 200 +#define HANDLE_SIZE 128 + +static ClutterActor *stage = NULL; +static ClutterActor *target1 = NULL; +static ClutterActor *target2 = NULL; +static ClutterActor *drag = NULL; + +static gboolean drop_successful = FALSE; + +static void add_drag_object (ClutterActor *target); + +static void +on_drag_end (ClutterDragAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y, + ClutterModifierType modifiers) +{ + ClutterActor *handle = clutter_drag_action_get_drag_handle (action); + + g_print ("Drag ended at: %.0f, %.0f\n", + event_x, event_y); + + clutter_actor_animate (actor, CLUTTER_LINEAR, 150, "opacity", 255, NULL); + + if (!drop_successful) + { + gfloat x_pos, y_pos; + + clutter_actor_animate (clutter_actor_get_parent (actor), + CLUTTER_LINEAR, 150, + "opacity", 255, + NULL); + + clutter_actor_get_transformed_position (actor, &x_pos, &y_pos); + clutter_actor_animate (handle, CLUTTER_EASE_OUT_BOUNCE, 250, + "x", x_pos, + "y", y_pos, + "opacity", 0, + "signal-swapped::completed", + G_CALLBACK (clutter_actor_destroy), + handle, + NULL); + } + else + { + clutter_actor_animate (handle, CLUTTER_LINEAR, 250, + "opacity", 0, + "signal-swapped::completed", + G_CALLBACK (clutter_actor_destroy), + handle, + NULL); + } +} + +static void +on_drag_begin (ClutterDragAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y, + ClutterModifierType modifiers) +{ + ClutterActor *handle; + gfloat x_pos, y_pos; + + clutter_actor_get_position (actor, &x_pos, &y_pos); + + handle = clutter_rectangle_new_with_color (CLUTTER_COLOR_DarkSkyBlue); + clutter_actor_set_size (handle, 128, 128); + clutter_actor_set_position (handle, event_x - x_pos, event_y - y_pos); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), handle); + + clutter_drag_action_set_drag_handle (action, handle); + + clutter_actor_animate (actor, CLUTTER_LINEAR, 150, "opacity", 128, NULL);\ + + drop_successful = FALSE; +} + +static void +add_drag_object (ClutterActor *target) +{ + ClutterActor *parent; + + if (drag == NULL) + { + ClutterAction *action; + + drag = clutter_rectangle_new_with_color (CLUTTER_COLOR_LightSkyBlue); + clutter_actor_set_size (drag, HANDLE_SIZE, HANDLE_SIZE); + clutter_actor_set_position (drag, + (TARGET_SIZE - HANDLE_SIZE) / 2.0, + (TARGET_SIZE - HANDLE_SIZE) / 2.0); + clutter_actor_set_reactive (drag, TRUE); + + action = clutter_drag_action_new (); + g_signal_connect (action, "drag-begin", G_CALLBACK (on_drag_begin), NULL); + g_signal_connect (action, "drag-end", G_CALLBACK (on_drag_end), NULL); + + clutter_actor_add_action (drag, action); + } + + parent = clutter_actor_get_parent (drag); + + if (parent == target) + { + clutter_actor_animate (target, CLUTTER_LINEAR, 150, + "opacity", 255, + NULL); + return; + } + + g_object_ref (drag); + if (parent != NULL && parent != stage) + { + clutter_container_remove_actor (CLUTTER_CONTAINER (parent), drag); + clutter_actor_animate (parent, CLUTTER_LINEAR, 150, + "opacity", 64, + NULL); + } + + clutter_container_add_actor (CLUTTER_CONTAINER (target), drag); + clutter_actor_animate (target, CLUTTER_LINEAR, 150, + "opacity", 255, + NULL); + + g_object_unref (drag); +} + +static void +on_target_over (ClutterDropAction *action, + ClutterActor *actor, + gpointer _data) +{ + gboolean is_over = GPOINTER_TO_UINT (_data); + guint8 final_opacity = is_over ? 128 : 64; + ClutterActor *target; + + target = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action)); + + clutter_actor_animate (target, CLUTTER_LINEAR, 250, + "opacity", final_opacity, + NULL); +} + +static void +on_target_drop (ClutterDropAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y) +{ + gfloat actor_x, actor_y; + + actor_x = actor_y = 0.0f; + + clutter_actor_transform_stage_point (actor, event_x, event_y, + &actor_x, + &actor_y); + + g_print ("Dropped at %.0f, %.0f (screen: %.0f, %.0f)\n", + actor_x, actor_y, + event_x, event_y); + + drop_successful = TRUE; + add_drag_object (actor); +} + +G_MODULE_EXPORT int +test_drop_main (int argc, char *argv[]) +{ + ClutterActor *dummy; + + if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) + return EXIT_FAILURE; + + stage = clutter_stage_new (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "Drop Action"); + g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); + + target1 = clutter_box_new (clutter_fixed_layout_new ()); + clutter_box_set_color (CLUTTER_BOX (target1), CLUTTER_COLOR_LightScarletRed); + clutter_actor_set_size (target1, TARGET_SIZE, TARGET_SIZE); + clutter_actor_set_opacity (target1, 64); + clutter_actor_add_constraint (target1, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5)); + clutter_actor_set_x (target1, 10); + clutter_actor_set_reactive (target1, TRUE); + + clutter_actor_add_action_with_name (target1, "drop", clutter_drop_action_new ()); + g_signal_connect (clutter_actor_get_action (target1, "drop"), + "over-in", + G_CALLBACK (on_target_over), + GUINT_TO_POINTER (TRUE)); + g_signal_connect (clutter_actor_get_action (target1, "drop"), + "over-out", + G_CALLBACK (on_target_over), + GUINT_TO_POINTER (FALSE)); + g_signal_connect (clutter_actor_get_action (target1, "drop"), + "drop", + G_CALLBACK (on_target_drop), + NULL); + + dummy = clutter_rectangle_new_with_color (CLUTTER_COLOR_DarkOrange); + clutter_actor_set_size (dummy, + 640 - (2 * 10) - (2 * (TARGET_SIZE + 10)), + TARGET_SIZE); + clutter_actor_add_constraint (dummy, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5)); + clutter_actor_add_constraint (dummy, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5)); + clutter_actor_set_reactive (dummy, TRUE); + + target2 = clutter_box_new (clutter_fixed_layout_new ()); + clutter_box_set_color (CLUTTER_BOX (target2), CLUTTER_COLOR_LightChameleon); + clutter_actor_set_size (target2, TARGET_SIZE, TARGET_SIZE); + clutter_actor_set_opacity (target2, 64); + clutter_actor_add_constraint (target2, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5)); + clutter_actor_set_x (target2, 640 - TARGET_SIZE - 10); + clutter_actor_set_reactive (target2, TRUE); + + clutter_actor_add_action_with_name (target2, "drop", clutter_drop_action_new ()); + g_signal_connect (clutter_actor_get_action (target2, "drop"), + "over-in", + G_CALLBACK (on_target_over), + GUINT_TO_POINTER (TRUE)); + g_signal_connect (clutter_actor_get_action (target2, "drop"), + "over-out", + G_CALLBACK (on_target_over), + GUINT_TO_POINTER (FALSE)); + g_signal_connect (clutter_actor_get_action (target2, "drop"), + "drop", + G_CALLBACK (on_target_drop), + NULL); + + clutter_container_add (CLUTTER_CONTAINER (stage), target1, dummy, target2, NULL); + + add_drag_object (target1); + + clutter_actor_show (stage); + + clutter_main (); + + return EXIT_SUCCESS; +}