Clean up crossing event synthesis code

Clutter should just require that the windowing system used by a backend
adds a device to the stage when the device enters, and removes it from
the stage when the device leaves; with this information, we can
synthesize every crossing event and update the device state without
other intervention from the backend-specific code.

The generation of additional crossing events for actors that are
covering the stage at the coordinates of the crossing event should be
delegated to the event processing code.

The x11 and win32 backends need to be modified to relay the enter and
leave events from the windowing system.
This commit is contained in:
Emmanuele Bassi 2011-01-27 17:26:16 +00:00
parent b3784bd2b4
commit 82d1e5a6ee
10 changed files with 238 additions and 157 deletions

View File

@ -136,8 +136,10 @@ void _clutter_input_device_set_time (ClutterInputDev
void _clutter_input_device_set_stage (ClutterInputDevice *device,
ClutterStage *stage);
void _clutter_input_device_set_actor (ClutterInputDevice *device,
ClutterActor *actor);
ClutterActor * _clutter_input_device_update (ClutterInputDevice *device);
ClutterActor *actor,
gboolean emit_crossing);
ClutterActor * _clutter_input_device_update (ClutterInputDevice *device,
gboolean emit_crossing);
void _clutter_input_device_set_n_keys (ClutterInputDevice *device,
guint n_keys);
guint _clutter_input_device_add_axis (ClutterInputDevice *device,

View File

@ -423,7 +423,7 @@ _clutter_device_manager_update_devices (ClutterDeviceManager *device_manager)
if (device->stage == NULL)
continue;
_clutter_input_device_update (device);
_clutter_input_device_update (device, TRUE);
}
}

View File

@ -543,6 +543,7 @@ clutter_event_set_device (ClutterEvent *event,
ClutterInputDevice *device)
{
g_return_if_fail (event != NULL);
g_return_if_fail (device == NULL || CLUTTER_IS_INPUT_DEVICE (device));
if (is_event_allocated (event))
{

View File

@ -445,49 +445,23 @@ void
_clutter_input_device_set_stage (ClutterInputDevice *device,
ClutterStage *stage)
{
ClutterStage *old_stage;
if (device->stage == stage)
return;
g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));
old_stage = device->stage;
device->stage = stage;
/* if we left the stage then we also need to unset the
* cursor actor (and update its :has-pointer property)
/* we leave the ->cursor_actor in place in order to check
* if we left the stage without crossing it again; this way
* we can emit a leave event on the cursor actor right before
* we emit the leave event on the stage.
*/
if (device->stage == NULL &&
device->cursor_actor != NULL &&
device->cursor_actor != CLUTTER_ACTOR (old_stage))
{
ClutterEvent cev;
cev.crossing.type = CLUTTER_LEAVE;
cev.crossing.time = device->current_time;
cev.crossing.flags = 0;
cev.crossing.stage = old_stage;
cev.crossing.source = device->cursor_actor;
cev.crossing.x = device->current_x;
cev.crossing.y = device->current_y;
cev.crossing.device = device;
cev.crossing.related = device->stage != NULL
? CLUTTER_ACTOR (device->stage)
: CLUTTER_ACTOR (old_stage);
_clutter_stage_queue_event (old_stage, &cev);
_clutter_actor_set_has_pointer (device->cursor_actor, FALSE);
g_object_weak_unref (G_OBJECT (device->cursor_actor),
cursor_weak_unref,
device);
}
device->cursor_actor = NULL;
}
/*< private >
* clutter_input_device_set_actor:
* @device: a #ClutterInputDevice
* @actor: a #ClutterActor
* @emit_crossing: %TRUE to emit crossing events
*
* Sets the actor under the pointer coordinates of @device
*
@ -504,34 +478,41 @@ _clutter_input_device_set_stage (ClutterInputDevice *device,
*/
void
_clutter_input_device_set_actor (ClutterInputDevice *device,
ClutterActor *actor)
ClutterActor *actor,
gboolean emit_crossing)
{
ClutterActor *old_actor;
ClutterEvent cev;
g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));
if (actor == device->cursor_actor)
if (device->cursor_actor == actor)
return;
old_actor = device->cursor_actor;
if (old_actor != NULL)
{
cev.crossing.type = CLUTTER_LEAVE;
cev.crossing.time = device->current_time;
cev.crossing.flags = 0;
cev.crossing.stage = device->stage;
cev.crossing.source = device->cursor_actor;
cev.crossing.x = device->current_x;
cev.crossing.y = device->current_y;
cev.crossing.device = device;
cev.crossing.related = actor;
if (emit_crossing)
{
ClutterEvent *event;
/* we need to make sure that this event is processed before
* any other event we might have queued up until now, so we
* go on and synthesize the event emission
*/
_clutter_process_event (&cev);
event = clutter_event_new (CLUTTER_LEAVE);
event->crossing.time = device->current_time;
event->crossing.flags = 0;
event->crossing.stage = device->stage;
event->crossing.source = device->cursor_actor;
event->crossing.x = device->current_x;
event->crossing.y = device->current_y;
event->crossing.related = actor;
clutter_event_set_device (event, device);
/* we need to make sure that this event is processed
* before any other event we might have queued up until
* now, so we go on, and synthesize the event emission
* ourselves
*/
_clutter_process_event (event);
clutter_event_free (event);
}
/* processing the event might have destroyed the actor */
if (device->cursor_actor != NULL)
@ -547,54 +528,28 @@ _clutter_input_device_set_actor (ClutterInputDevice *device,
if (actor != NULL)
{
cev.crossing.type = CLUTTER_ENTER;
cev.crossing.time = device->current_time;
cev.crossing.flags = 0;
cev.crossing.stage = device->stage;
cev.crossing.x = device->current_x;
cev.crossing.y = device->current_y;
cev.crossing.device = device;
CLUTTER_NOTE (EVENT, "Device '%s' entering '%s' at %d, %d",
device->device_name,
clutter_actor_get_name (actor) != NULL
? clutter_actor_get_name (actor)
: G_OBJECT_TYPE_NAME (actor),
device->current_x,
device->current_y);
/* if there is an actor overlapping the Stage boundary and we
* don't do this check then we'll emit an ENTER event only on
* the actor instead of emitting it on the Stage *and* the
* actor
*/
if (old_actor == NULL && actor != CLUTTER_ACTOR (device->stage))
if (emit_crossing)
{
cev.crossing.source = CLUTTER_ACTOR (device->stage);
cev.crossing.related = NULL;
ClutterEvent *event;
CLUTTER_NOTE (EVENT, "Adding Crossing[Enter] event for Stage");
event = clutter_event_new (CLUTTER_ENTER);
event->crossing.time = device->current_time;
event->crossing.flags = 0;
event->crossing.stage = device->stage;
event->crossing.x = device->current_x;
event->crossing.y = device->current_y;
event->crossing.source = actor;
event->crossing.related = old_actor;
clutter_event_set_device (event, device);
_clutter_process_event (&cev);
/* see above */
_clutter_process_event (event);
cev.crossing.source = actor;
cev.crossing.related = CLUTTER_ACTOR (device->stage);
clutter_event_free (event);
}
else
{
cev.crossing.source = actor;
cev.crossing.related = old_actor;
}
/* as above: we need to make sure that this event is processed
* before any other event we might have queued up until now, so
* we go on and synthesize the event emission
*/
_clutter_process_event (&cev);
}
device->cursor_actor = actor;
if (device->cursor_actor != NULL)
{
g_object_weak_ref (G_OBJECT (device->cursor_actor),
@ -730,7 +685,8 @@ clutter_input_device_get_device_coords (ClutterInputDevice *device,
* Since: 1.2
*/
ClutterActor *
_clutter_input_device_update (ClutterInputDevice *device)
_clutter_input_device_update (ClutterInputDevice *device,
gboolean emit_crossing)
{
ClutterStage *stage;
ClutterActor *new_cursor_actor;
@ -773,7 +729,7 @@ _clutter_input_device_update (ClutterInputDevice *device)
if (new_cursor_actor == old_cursor_actor)
return old_cursor_actor;
_clutter_input_device_set_actor (device, new_cursor_actor);
_clutter_input_device_set_actor (device, new_cursor_actor, emit_crossing);
return device->cursor_actor;
}

View File

@ -2154,7 +2154,7 @@ emit_event (ClutterEvent *event,
/* reentrancy check */
if (lock != FALSE)
{
g_warning ("Tried emitting event during event delivery, bailing out.n");
g_warning ("Tried emitting event during event delivery, bailing out.");
return;
}
@ -2248,10 +2248,14 @@ is_off_stage (ClutterActor *stage,
gfloat x,
gfloat y)
{
gfloat width, height;
clutter_actor_get_size (stage, &width, &height);
return (x < 0 ||
y < 0 ||
x >= clutter_actor_get_width (stage) ||
y >= clutter_actor_get_height (stage));
x >= width ||
y >= height);
}
/**
@ -2293,18 +2297,6 @@ _clutter_process_event_details (ClutterActor *stage,
event->any.source = stage;
break;
case CLUTTER_LEAVE:
case CLUTTER_ENTER:
emit_pointer_event (event, device);
break;
case CLUTTER_DESTROY_NOTIFY:
case CLUTTER_DELETE:
event->any.source = stage;
/* the stage did not handle the event, so we just quit */
clutter_stage_event (CLUTTER_STAGE (stage), event);
break;
case CLUTTER_KEY_PRESS:
case CLUTTER_KEY_RELEASE:
{
@ -2326,6 +2318,65 @@ _clutter_process_event_details (ClutterActor *stage,
}
break;
case CLUTTER_ENTER:
/* if we're entering from outside the stage we need
* to check whether the pointer is actually on another
* actor, and emit an additional pointer event
*/
if (event->any.source == stage &&
event->crossing.related == NULL)
{
ClutterActor *actor = NULL;
emit_pointer_event (event, device);
actor = _clutter_input_device_update (device, FALSE);
if (actor != stage)
{
ClutterEvent *crossing;
/* we emit the exact same event on the actor */
crossing = clutter_event_copy (event);
crossing->crossing.related = stage;
crossing->crossing.source = actor;
emit_pointer_event (crossing, device);
clutter_event_free (crossing);
}
}
else
emit_pointer_event (event, device);
break;
case CLUTTER_LEAVE:
/* same as CLUTTER_ENTER above: when leaving the stage
* we need to also emit a CLUTTER_LEAVE event on the
* actor currently underneath the device, unless it's the
* stage
*/
if (event->any.source == stage &&
event->crossing.related == NULL &&
device->cursor_actor != stage)
{
ClutterEvent *crossing;
crossing = clutter_event_copy (event);
crossing->crossing.related = stage;
crossing->crossing.source = device->cursor_actor;
emit_pointer_event (crossing, device);
clutter_event_free (crossing);
}
emit_pointer_event (event, device);
break;
case CLUTTER_DESTROY_NOTIFY:
case CLUTTER_DELETE:
event->any.source = stage;
/* the stage did not handle the event, so we just quit */
clutter_stage_event (CLUTTER_STAGE (stage), event);
break;
case CLUTTER_MOTION:
/* Only stage gets motion events if clutter_set_motion_events is TRUE,
* and the event is not a synthetic event with source set.
@ -2413,7 +2464,7 @@ _clutter_process_event_details (ClutterActor *stage,
* get the actor underneath
*/
if (device != NULL)
actor = _clutter_input_device_update (device);
actor = _clutter_input_device_update (device, TRUE);
else
{
CLUTTER_NOTE (EVENT, "No device found: picking");
@ -2434,11 +2485,6 @@ _clutter_process_event_details (ClutterActor *stage,
actor = event->any.source;
}
/* FIXME: for an optimisation should check if there are
* actually any reactive actors and avoid the pick all together
* (signalling just the stage). Should be big help for gles.
*/
CLUTTER_NOTE (EVENT,
"Reactive event received at %.2f, %.2f - actor: %p",
x, y,
@ -2507,7 +2553,7 @@ _clutter_process_event (ClutterEvent *event)
*
* Since: 0.6
*/
ClutterActor*
ClutterActor *
clutter_get_actor_by_gid (guint32 id)
{
ClutterMainContext *context;

View File

@ -24,6 +24,7 @@
#include <clutter/clutter-stage-window.h>
#include <clutter/clutter-stage.h>
#include <clutter/clutter-input-device.h>
G_BEGIN_DECLS
@ -82,6 +83,13 @@ ClutterStageQueueRedrawEntry *_clutter_stage_queue_actor_redraw (Clut
ClutterPaintVolume *clip);
void _clutter_stage_queue_redraw_entry_invalidate (ClutterStageQueueRedrawEntry *entry);
void _clutter_stage_add_device (ClutterStage *stage,
ClutterInputDevice *device);
void _clutter_stage_remove_device (ClutterStage *stage,
ClutterInputDevice *device);
gboolean _clutter_stage_has_device (ClutterStage *stage,
ClutterInputDevice *device);
G_END_DECLS
#endif /* __CLUTTER_STAGE_PRIVATE_H__ */

View File

@ -135,6 +135,8 @@ struct _ClutterStagePrivate
ClutterPickMode pick_buffer_mode;
GHashTable *devices;
guint relayout_pending : 1;
guint redraw_pending : 1;
guint is_fullscreen : 1;
@ -1202,10 +1204,13 @@ clutter_stage_finalize (GObject *object)
g_queue_foreach (priv->event_queue, (GFunc)clutter_event_free, NULL);
g_queue_free (priv->event_queue);
g_free (stage->priv->title);
g_free (priv->title);
g_array_free (priv->paint_volume_stack, TRUE);
if (priv->devices != NULL)
g_hash_table_destroy (priv->devices);
G_OBJECT_CLASS (clutter_stage_parent_class)->finalize (object);
}
@ -3375,3 +3380,44 @@ clutter_stage_get_accept_focus (ClutterStage *stage)
return stage->priv->accept_focus;
}
void
_clutter_stage_add_device (ClutterStage *stage,
ClutterInputDevice *device)
{
ClutterStagePrivate *priv = stage->priv;
if (G_UNLIKELY (priv->devices == NULL))
priv->devices = g_hash_table_new (NULL, NULL);
if (g_hash_table_lookup (priv->devices, device) != NULL)
return;
g_hash_table_insert (priv->devices, device, GINT_TO_POINTER (1));
_clutter_input_device_set_stage (device, stage);
}
void
_clutter_stage_remove_device (ClutterStage *stage,
ClutterInputDevice *device)
{
ClutterStagePrivate *priv = stage->priv;
if (G_UNLIKELY (priv->devices == NULL))
return;
_clutter_input_device_set_stage (device, NULL);
g_hash_table_remove (priv->devices, device);
}
gboolean
_clutter_stage_has_device (ClutterStage *stage,
ClutterInputDevice *device)
{
ClutterStagePrivate *priv = stage->priv;
if (priv->devices == NULL)
return FALSE;
return g_hash_table_lookup (priv->devices, device) != NULL;
}

View File

@ -548,6 +548,7 @@ clutter_win32_handle_event (const MSG *msg)
we're not already */
if (!stage_win32->tracking_mouse)
{
ClutterEvent *crossing = clutter_event_new (CLUTTER_ENTER);
TRACKMOUSEEVENT tmevent;
tmevent.cbSize = sizeof (tmevent);
@ -555,8 +556,18 @@ clutter_win32_handle_event (const MSG *msg)
tmevent.hwndTrack = stage_win32->hwnd;
TrackMouseEvent (&tmevent);
event->crossing.time = msg->time;
event->crossing.x = event->motion.x;
event->crossing.y = event->motion.y;
event->crossing.device = event->motion.device;
event->crossing.stage = stage;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.related = NULL;
/* we entered the stage */
_clutter_input_device_set_stage (event->motion.device, stage);
_clutter_stage_add_device (stage, event->crossing.device);
take_and_queue_event (crossing);
stage_win32->tracking_mouse = TRUE;
}
@ -573,10 +584,12 @@ clutter_win32_handle_event (const MSG *msg)
event->crossing.x = msg->pt.x;
event->crossing.y = msg->pt.y;
event->crossing.device = core_pointer;
event->any.stage = stage;
event->crossing.stage = stage;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.related = NULL;
/* we left the stage */
_clutter_input_device_set_stage (event->crossing.device, NULL);
_clutter_stage_remove_device (stage, event->crossing.device);
/* When we get a leave message the mouse tracking is
automatically cancelled so we'll need to start it again when

View File

@ -220,7 +220,8 @@ translate_key_event (ClutterBackendX11 *backend_x11,
event->key.type = xevent->xany.type == KeyPress ? CLUTTER_KEY_PRESS
: CLUTTER_KEY_RELEASE;
event->key.time = xevent->xkey.time;
event->key.device = manager_x11->core_keyboard;
clutter_event_set_device (event, manager_x11->core_keyboard);
/* KeyEvents have platform specific data associated to them */
event_x11 = _clutter_event_x11_new ();
@ -406,7 +407,7 @@ clutter_device_manager_x11_translate_event (ClutterEventTranslator *translator,
event->scroll.x = xevent->xbutton.x;
event->scroll.y = xevent->xbutton.y;
event->scroll.modifier_state = xevent->xbutton.state;
event->scroll.device = manager_x11->core_pointer;
event->scroll.axes = NULL;
break;
default:
@ -416,11 +417,12 @@ clutter_device_manager_x11_translate_event (ClutterEventTranslator *translator,
event->button.y = xevent->xbutton.y;
event->button.modifier_state = xevent->xbutton.state;
event->button.button = xevent->xbutton.button;
event->button.device = manager_x11->core_pointer;
event->button.axes = NULL;
break;
}
clutter_event_set_device (event, manager_x11->core_pointer);
_clutter_stage_x11_set_user_time (stage_x11, xevent->xbutton.time);
res = CLUTTER_TRANSLATE_QUEUE;
break;
@ -450,7 +452,7 @@ clutter_device_manager_x11_translate_event (ClutterEventTranslator *translator,
event->button.modifier_state = xevent->xbutton.state;
event->button.button = xevent->xbutton.button;
event->button.axes = NULL;
event->button.device = manager_x11->core_pointer;
clutter_event_set_device (event, manager_x11->core_pointer);
res = CLUTTER_TRANSLATE_QUEUE;
break;
@ -466,25 +468,25 @@ clutter_device_manager_x11_translate_event (ClutterEventTranslator *translator,
event->motion.x = xevent->xmotion.x;
event->motion.y = xevent->xmotion.y;
event->motion.modifier_state = xevent->xmotion.state;
event->motion.device = manager_x11->core_pointer;
event->motion.axes = NULL;
clutter_event_set_device (event, manager_x11->core_pointer);
res = CLUTTER_TRANSLATE_QUEUE;
break;
case EnterNotify:
/* we know that we are entering the stage here */
_clutter_input_device_set_stage (manager_x11->core_pointer, stage);
CLUTTER_NOTE (EVENT, "Entering the stage");
CLUTTER_NOTE (EVENT, "Entering the stage (time:%u)",
(unsigned int) xevent->xcrossing.time);
event->crossing.type = CLUTTER_ENTER;
event->crossing.time = xevent->xcrossing.time;
event->crossing.x = xevent->xcrossing.x;
event->crossing.y = xevent->xcrossing.y;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.related = NULL;
clutter_event_set_device (event, manager_x11->core_pointer);
_clutter_stage_add_device (stage, manager_x11->core_pointer);
/* Convert enter notifies to motion events because X
doesn't emit the corresponding motion notify */
event->motion.type = event->type = CLUTTER_MOTION;
event->motion.time = xevent->xcrossing.time;
event->motion.x = xevent->xcrossing.x;
event->motion.y = xevent->xcrossing.y;
event->motion.modifier_state = xevent->xcrossing.state;
event->motion.source = CLUTTER_ACTOR (stage);
event->motion.device = manager_x11->core_pointer;
res = CLUTTER_TRANSLATE_QUEUE;
break;
@ -498,16 +500,19 @@ clutter_device_manager_x11_translate_event (ClutterEventTranslator *translator,
}
/* we know that we are leaving the stage here */
_clutter_input_device_set_stage (manager_x11->core_pointer, NULL);
CLUTTER_NOTE (EVENT, "Leaving the stage (time:%u)",
event->crossing.time);
(unsigned int) xevent->xcrossing.time);
event->crossing.type = CLUTTER_LEAVE;
event->crossing.time = xevent->xcrossing.time;
event->crossing.x = xevent->xcrossing.x;
event->crossing.y = xevent->xcrossing.y;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.device = manager_x11->core_pointer;
event->crossing.related = NULL;
clutter_event_set_device (event, manager_x11->core_pointer);
_clutter_stage_remove_device (stage, manager_x11->core_pointer);
res = CLUTTER_TRANSLATE_QUEUE;
break;

View File

@ -847,22 +847,22 @@ clutter_device_manager_xi2_translate_event (ClutterEventTranslator *translator,
device = g_hash_table_lookup (manager_xi2->devices_by_id,
GINT_TO_POINTER (xev->deviceid));
source_device = g_hash_table_lookup (manager_xi2->devices_by_id,
GINT_TO_POINTER (xev->sourceid));
if (xi_event->evtype == XI_Enter)
{
_clutter_input_device_set_stage (device, stage);
event->crossing.type = event->type = CLUTTER_ENTER;
event->motion.type = event->type = CLUTTER_MOTION;
event->crossing.stage = stage;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.related = NULL;
event->motion.stage = stage;
event->motion.source = CLUTTER_ACTOR (stage);
event->crossing.time = xev->time;
event->crossing.x = xev->event_x;
event->crossing.y = xev->event_y;
event->motion.time = xev->time;
event->motion.x = xev->event_x;
event->motion.y = xev->event_y;
event->motion.device = device;
event->motion.modifier_state =
_clutter_input_device_xi2_translate_state (&xev->mods,
&xev->buttons);
_clutter_stage_add_device (stage, device);
}
else
{
@ -876,18 +876,22 @@ clutter_device_manager_xi2_translate_event (ClutterEventTranslator *translator,
break;
}
_clutter_input_device_set_stage (device, NULL);
event->crossing.type = event->type = CLUTTER_LEAVE;
event->crossing.stage = stage;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.related = NULL;
event->crossing.time = xev->time;
event->crossing.x = xev->event_x;
event->crossing.y = xev->event_y;
event->crossing.device = device;
_clutter_stage_remove_device (stage, device);
}
clutter_event_set_device (event, device);
_clutter_event_set_source_device (event, source_device);
retval = CLUTTER_TRANSLATE_QUEUE;
}
break;