Events The Tenth Doctor (David Tennant) Crossing into established events is strictly forbidden. Except for cheap tricks.
Introduction Once you have set up a scene on the stage, in order to respond to user interaction you will have to handle events coming from the underlying platform. Events are relayed to actors by Clutter in form of signals; signals are a facility provided by the GObject framework to call functions depending on a unique name. A signal can be thought as a message that an object instance broadcasts to various listener functions. There are various events that Clutter will handle: mostly, they deal with input devices, like a mouse pointer or a keyboard; but they can also come from the windowing system, like the delete-event signal that is emitted when the user closes the window of the stage. Each event has a particular source, that is the actor that received the event. The event handling sequence is divided in two phases: the capture phase, which consists in an emission of the captured-event signal starting from the stage to, following the parent-child relationship, the source of the event; the bubble phase, which consists in an emission of the event signal starting from the source of the event to, following the parent-child relationship, the stage. At any point during the event emission sequence a handler of either the captured-event or the event signals can stop it, by returning a boolean value of true, which means that the event has been handled. If an event hasn't been handled, a boolean value of false should be returned instead. Clutter provides two useful macros to avoid remembering which boolean value should be used in an event signal handler: CLUTTER_EVENT_PROPAGATE, equivalent to FALSE; and CLUTTER_EVENT_STOP, equivalent to TRUE.
Handling key events
Problem You want to respond to key presses on an actor.
Solutions There are two possible solutions: Solution 1: Connect a callback to the actor; inside the callback, manually analyse which key and modifier(s) were pressed and react accordingly. Solution 2: Use an actor's ClutterBindingPool to declaratively assign actions to specific key and modifier combinations. Each solution is covered below.
Solution 1 Connect the key-press-event signal for an actor to a callback; then examine the event in the callback to determine which key and modifiers were pressed. First, connect an actor's key-press-event signal to a callback: g_signal_connect (actor, "key-press-event", G_CALLBACK (_key_press_cb), NULL); Then, in the callback, check which key was pressed and which modifiers were down at the same time. For example, this callback checks for a press on the up arrow key and whether the Shift and/or Ctrl key were down: Clutter provides a range of key value definitions (like CLUTTER_KEY_Up, used above). These are generated from the list in the X.Org source code (replace "XK" with "CLUTTER_KEY" in the definitions there to get the Clutter equivalents; alternatively, look at the clutter-keysyms.h header file for the list). CLUTTER_SHIFT_MASK, CLUTTER_CONTROL_MASK and other modifiers are defined in the ClutterModifierType enum.
Solution 2 Assign actions to an actor's ClutterBindingPool. A binding pool stores mappings from a key press (either a single key or a key plus modifiers) to actions; an action is simply a callback function with a specific signature. While this approach is trickier to implement, it is more flexible and removes the drudgery of writing branching code to handle different key presses. See the Discussion section for more details. To use this approach with an actor which will receive key press events, first get that actor's binding pool. In the example below, we're using the binding pool for the default ClutterStage: ClutterBindingPool *binding_pool; GObjectClass *stage_class; stage_class = CLUTTER_STAGE_GET_CLASS (stage); binding_pool = clutter_binding_pool_get_for_class (stage_class); Next, install actions into the binding pool. For example, to install an action bound to the up arrow key, which calls the _move_up() function when that key is pressed, you would do: clutter_binding_pool_install_action (binding_pool, "move-up", /* identifier */ CLUTTER_KEY_Up, /* up arrow pressed */ 0, /* no modifiers pressed */ G_CALLBACK (_move_up), NULL, /* no user data passed */ NULL); Another example, binding up arrow + Shift + Ctrl to an action which calls _move_up_shift_control() when activated: clutter_binding_pool_install_action (binding_pool, "move-up-shift-control", CLUTTER_KEY_Up, CLUTTER_SHIFT_MASK + CLUTTER_CONTROL_MASK, G_CALLBACK (_move_up_shift_control), NULL, NULL); The function called when an action is activated looks like this (for _move_up()): static void _move_up (GObject *instance, const gchar *action_name, guint key_val, ClutterModifierType modifiers, gpointer user_data) { g_debug ("Up pressed"); } Then bind the key-press-event signal for the actor (in our case, the stage) to a callback: g_signal_connect (stage, "key-press-event", G_CALLBACK (_key_press_cb), NULL); Finally, inside the callback, pass control to the actor's binding pool rather than dissecting the key press event yourself: static gboolean _key_press_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { ClutterBindingPool *pool; pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor)); return clutter_binding_pool_activate (pool, clutter_event_get_key_symbol (event), clutter_event_get_state (event), G_OBJECT (actor)); } Now, when a key + modifiers that have been bound to an action are pressed on the actor, the appropriate action is activated.
Discussion
Pros and cons of Solution 1 and Solution 2 Solution 1 is the simplest (in terms of the amount of code you have to write for simple cases), but could quickly turn into a mess if you need many conditions or want to capture many key combinations. Also, if multiple actors need to respond to key press events, you'll need similar event dissection code in each callback. Solution 2 is more complicated to implement, but scales better if you have many different key combinations on multiple actors. The binding pool protects you from the minutiae of detecting which keys were pressed, leaving you to concentrate on the triggered actions instead. This could simplify your control logic. In addition, Solution 2 lets you write a single callback to handle all key press events for all actors. This callback could then use clutter_binding_pool_find() (as in the example code) to determine which binding pool to activate (depending on which actor received the key press event). Finally, a binding pool allows you to block and unblock actions. This means you can make the response to a key press event conditional on application state. For example, let's say you wanted the up arrow key to move an actor, but only when the actor is at the bottom of the stage. To implement this, you could disable the up arrow key action in the binding pool initially; then, once the actor reaches the bottom of the stage, enable the up arrow key action again. While this is possible with Solution 1, you would have to implement more of the state management code yourself.
Other useful things to know about key press events A ClutterKeyEvent contains only a single key value, plus possibly one or more modifier keys (like Shift, Ctrl, Alt etc.). There are no functions in the Clutter API which return events for tracking near-simultaneous presses on multiple keys. By default, the stage receives all key events. To make another actor receive key events, use clutter_stage_set_key_focus(): /* * stage is a ClutterStage instance; * actor is the ClutterActor instance which should receive key events */ clutter_stage_set_key_focus (stage, actor);
Detecting mouse scrolling on an actor
Problem You want to detect when the mouse is scrolled on an actor (e.g. the pointer is over an actor when a mouse wheel is scrolled).
Solution Connect a callback handler to the scroll-event signal of an actor. First, ensure that the actor is reactive (i.e. will respond to events): Next, create a callback handler to examine the scroll event and respond to it: Finally, connect the callback handler to the scroll-event signal of the actor:
Discussion A standard mouse wheel will only return up and down movements; but in cases where the mouse has left and right scrolling (e.g. a trackball mouse or trackpad), left and right scroll events may also be emitted.
Creating a scrolling viewport for an actor While the simple outline above explains the basics of how to connect to scroll events, it doesn't do much to help with really implementing scrolling over an actor. That's what we'll do in this section. The full code for the example we'll walk through here is available in this later section. Scrolling over an actor actually requires coordination between two components: Scrollable actor An actor which is too large to fit on the stage or inside the area of the UI assigned to it (otherwise there's no need to scroll over it...). Viewport This displays a cropped view of part of the scrollable actor, revealing different parts of it as scroll events occur. Here are the steps required to set up the two actors: Create the scrollable actor; it should be larger than the viewport. This example uses a ClutterTexture, but any ClutterActor will work: /* get image file path, set up stage etc. */ ClutterActor *texture; texture = clutter_texture_new (); clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (texture), TRUE); /* * set the texture's height so it's as tall as the stage * (STAGE_HEIGHT is define'd at the top of the file) */ clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_WIDTH_FOR_HEIGHT); clutter_actor_set_height (texture, STAGE_HEIGHT); /* * load the image file; * see this recipe for more about loading images into textures */ clutter_texture_set_from_file (CLUTTER_TEXTURE (texture), image_file_path, NULL); Create the viewport. The simplest way to do this is with a ClutterGroup: The key here is calling clutter_actor_set_clip_to_allocation (viewport, TRUE). This configures the viewport group so that any of its children are clipped: i.e. only parts of its children which fit inside its allocation are visible. This in turn requires setting an explicit size on the group, rather than allowing it to size itself to fit its children (the latter is the default). Put the scrollable actor into the viewport; and the viewport into its container (in this case, the default stage): clutter_container_add_actor (CLUTTER_CONTAINER (viewport), texture); clutter_container_add_actor (CLUTTER_CONTAINER (stage), viewport); Create a callback handler for scroll-event signals emitted by the viewport: The approach taken here is to move the scrollable actor up, relative to the viewport. Initially, the scrollable will have a y coordinate value of 0.0 (aligned to the top of the viewport). Scrolling up decrements the y coordinate (down to a minumum of viewport_height - scrollable_height). This moves the top of the scrollable actor "outside" the clip area of the viewport; simultaneously, more of the bottom part of the scrollable moves into the clip area, becoming visible. Scrolling down increments the y coordinate (but only up to a maximum value of 0.0). To see how this works in practice, look at the code sample. There, the height of the scrollable actor is set to 300 and the height of the viewport to 150. This means that the y coordinate value for the scrollable actor will vary between -150.0: 150 (the viewport's height) - 300 (the scrollable actor's height), making its base visible and clipping its top; and 0.0, where its top is visible and its base clipped. Connect the callback handler to the signal; note that we pass the scrollable actor (the texture) to the callback, as we're moving the texture relative to the viewport to create the scrolling effect: g_signal_connect (viewport, "scroll-event", G_CALLBACK (_scroll_event_cb), texture); Here's a video of the result: Video showing a scrollable actor
Full example Mouse scrolling over a <type>ClutterActor</type> a code sample should be here... but isn't
Detecting pointer movements on an actor
Problem You want to be able to tell when the pointer (e.g. associated with a mouse or touches on a screen) enters, leaves, or moves over an actor. Example use cases include: Adding a tooltip or hover effect to an actor when a pointer moves onto it. Tracing the path of the pointer over an actor (e.g. in a drawing application).
Solution Connect to the pointer motion signals emitted by the actor.
Responding to crossing events To detect the pointer crossing the boundary of an actor (entering or leaving), connect to the enter-event and/or leave-event signals. For example: ClutterActor *actor = clutter_texture_new (); /* ...set size, color, image etc., depending on the actor... */ /* make the actor reactive: see Discussion for more details */ clutter_actor_set_reactive (actor, TRUE); /* connect to the signals */ g_signal_connect (actor, "enter-event", G_CALLBACK (_pointer_enter_cb), NULL); g_signal_connect (actor, "leave-event", G_CALLBACK (_pointer_leave_cb), NULL); The signature for callbacks connected to each of these signals is: gboolean _on_crossing (ClutterActor *actor, ClutterEvent *event, gpointer user_data) In the callback, you can examine the event to get the coordinates where the pointer entered or left the actor. For example, _pointer_enter_cb() could follow this template: See the code example in the appendix for an example of how you can implement a hover effect on a "button" (rectangle with text overlay) using this approach.
Responding to motion events Motion events occur when a pointer moves over an actor; the actor emits a motion-event signal when this happens. To respond to motion events, connect to this signal: /* set up the actor, make reactive etc., as above */ /* connect to motion-event signal */ g_signal_connect (actor, "motion-event", G_CALLBACK (_pointer_motion_cb), transitions); The signature of the callback is the same as for the enter-event/leave-event signals, so you can use code similar to the above to handle it. However, the type of the event is a ClutterMotionEvent (rather than a ClutterCrossingEvent).
Discussion A few more useful things to know about pointer motion events: Each crossing event is accompanied by a motion event at the same coordinates. Before an actor will emit signals for pointer events, it needs to be made reactive with: clutter_actor_set_reactive (actor, TRUE); A pointer event structure includes other data. Some examples: /* keys and mouse buttons pressed down when the pointer moved */ ClutterModifierType modifiers = clutter_event_get_state (event); /* time (since the epoch) when the event occurred */ guint32 event_time = clutter_event_get_time (event); /* actor where the event originated */ ClutterActor *actor = clutter_event_get_actor (event); /* stage where the event originated */ ClutterStage *stage = clutter_event_get_stage (event); There's no need to cast the event to use these functions: they will work on any ClutterEvent. The coordinates of an event (as returned by clutter_event_get_coords()) are relative to the stage where they originated, rather than the actor. Unless the actor is the same size as the stage, you'll typically want the actor-relative coordinates instead. To get those, use clutter_actor_transform_stage_point(). The simple scribble application gives a more thorough example of how to integrate pointer events into a Clutter application (in this case, for drawing on a ClutterTexture). The effect of actor depth on pointer motion events is worth slightly deeper discussion, and is covered next.
Pointer events on actors at different depths If you have actors stacked on top of each other, the reactive actor nearest the "top" is the one which emits the signal (when the pointer crosses into or moves over it). "Top" here means either at the top of the depth ordering (if all actors are at the same depth) or the closest to the view point (if actors have different depths in the z axis). Here's an example of three rectangles overlapping each other: Pointer events in actors with different depth ordering The rectangles are all at the same point on the z axis but stacked (different positions in the depth order). They have the following properties: The red rectangle is lowest down the depth ordering and reactive. Pointer motion signals are emitted by this actor when the pointer crosses or moves on the area of the rectangle not overlapped by the green rectangle. The green rectangle is in the middle of the depth ordering and reactive. This actor emits events over its whole surface, even though it is overlapped by the blue rectangle (as the blue rectangle is not reactive). Even if the blue rectangle were fully opaque, a pointer crossing into or moving on the green rectangle's area (even if obscured by the blue rectangle) would still cause a signal to be emitted. The blue rectangle is at the top of the depth ordering and not reactive. This actor doesn't emit any pointer motion signals and doesn't block events from occurring on any other actor. See the sample code in the appendix for more details.
Full examples Simple button with a hover animation (change in opacity as the pointer enters and leaves it) a code sample should be here... but isn't Detecting pointer motion on a <type>ClutterRectangle</type> a code sample should be here... but isn't How actors influence pointer events on each other a code sample should be here... but isn't Scribbling on a <type>ClutterTexture</type> in response to pointer events a code sample should be here... but isn't
Making an actor respond to button events
Problem You want an actor to respond to button events. These might be buttons on an input device like a mouse; or input events caused by other means, like touches on a screen. Some examples of where this is useful: For implementing button widgets which respond to button clicks. To make actor selections by mouse click (e.g. as part of a drawing application). To recognise a button press followed by pointer motion and button release (e.g. to implement drag and drop or kinetic animations).
Solution Connect a handler to the button-press-event and/or button-release-event signals of an actor. The button-press-event is emitted when a button is pressed (not necessarily released) on a reactive actor; the button-release-event when a button is released on a reactive actor (even if the button was pressed down somewhere else). First, ensure the actor is reactive: clutter_actor_set_reactive (actor, TRUE); Next, create a function to handle the signal(s) you are interested in. The function signature is the same for both the press and release signals: gboolean callback_function (ClutterActor *actor, ClutterEvent *event, gpointer user_data); You can use a single function as the callback for both signals (or write a different one for each signal). Here's an example function which can be used as a callback for both press and release signals, as it simply pulls data out of the event and displays it: /* event is a ClutterButtonEvent * for both the press and the release signal; it contains * data about where the event occurred */ static gboolean button_event_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { gfloat x, y; gchar *event_type; guint button_pressed; ClutterModifierType state; gchar *ctrl_pressed; guint32 click_count; /* where the pointer was (relative to the stage) * when the button event occurred; use * clutter_actor_transform_stage_point() * to transform to actor-relative coordinates */ clutter_event_get_coords (event, &x, &y); /* check whether it was a press or release event */ event_type = "released"; if (clutter_event_type (event) == CLUTTER_BUTTON_PRESS) event_type = "pressed"; /* which button triggered the event */ button_pressed = clutter_event_get_button (event); /* keys down when the event occurred; * this is a bit mask composed of the bits for each key held down * when the button was pressed or released; see the * ClutterModifierType enum in the Clutter API docs * for a list of the available modifiers */ state = clutter_event_get_state (event); ctrl_pressed = "ctrl not pressed"; if (state & CLUTTER_CONTROL_MASK) ctrl_pressed = "ctrl pressed"; /* click count */ click_count = clutter_event_get_click_count (event); g_debug ("button %d was %s at %.0f,%.0f; %s; click count %d", button_pressed, event_type, x, y, ctrl_pressed, click_count); return CLUTTER_EVENT_STOP; } Finally, connect the signals to the function(s): /* connect the press event */ g_signal_connect (actor, "button-press-event", G_CALLBACK (button_event_cb), NULL); /* connect the release event */ g_signal_connect (actor, "button-release-event", G_CALLBACK (button_event_cb), NULL); Pressing or releasing a button on the actor will now trigger a call to the button_event_cb() function. See the full example for more details.
Discussion Properties of the ClutterButtonEvent emitted by both signals should be examined using the clutter_event_* functions (rather than struct members directly), as in the example above. While most of these functions are self-explanatory, a couple require more explanation: see the sections below. Also covered below is an alternative approach to handling a button press followed by a release on a single actor: by adding a ClutterClickAction to an actor. See this section for details. Finally, a longer example is included, showing how to make use of button press, button release and pointer events in a simple drawing application.
Click count The click count records the number of times a press/release pair occurred in sequence. You can retrieve it via the clutter_event_get_click_count() function. A press/release pair is effectively a click, so this term will be used from now on throughout this section, to make the explanation simpler. However, the click count has nothing to do with ClutterClickActions, described later. For clicks to be considered part of the same sequence (for the purposes of counting), all the clicks after the first one must occur within the global double_click_distance (pixels) of the first click; and the time between click n and click n+1 must be <= the global double_click_time (milliseconds). The clicks do not have to occur on the same actor: providing they occur within the double click distance and time, they are counted as part of the same click sequence. Also note that the clicks don't even have to happen on a reactive actor: providing they happen somewhere on the stage, they will still increment the click count. The default double click time and distance are stored in the ClutterSettings associated with an application. You can get/set their values like this: gint double_click_distance; gint double_click_time; ClutterSettings *settings = clutter_settings_get_default (); /* get double click settings */ g_object_get (settings, "double-click-distance", &double_click_distance, "double-click-time", &double_click_time, NULL); /* set */ g_object_set (settings, "double-click-distance", 50, "double-click-time", 1000, NULL);
Button numbering clutter_event_get_button() returns an integer representing the pressed or released button. In the case of a standard scroll mouse, the numbers returned are reliable across different hardware models: 1 = left mouse button 2 = scroll wheel 3 = right mouse button For mice with more buttons, or other types of input devices, the mappings may not be so straightforward: you may have to experiment to see which button returns which value.
<type>ClutterClickAction</type> ClutterActions add flexible event handling to ClutterActors. They recognise and abstract common sequences of low-level events into a single, more easily managed high-level event. In the case of a ClutterClickAction, the abstraction is over a press followed by a release on a single actor. This is achieved by "synthesising" the press and release signals on the actor: in other words, the action captures those two signals when emitted by a single actor; and, once captured, the action emits a single clicked signal instead of the two signals being emitted by the actor. The pointer can move off the actor between the press and release, but the press and release must both occur on the same actor, with no intervening presses or releases on other actors. In addition, there are no maximum distance or time constraints on the press and release. If a press occurs and you want to force it to be released (e.g. to break a pointer grab after a certain length of time has elapsed), use clutter_click_action_release(). On the down side, the clicked signal doesn't present the same detailed ClutterButtonEvent to the handler. So, for example, you can't get a click count from a ClutterClickAction (though you could count the clicks yourself, of course); and you don't have access to the coordinates where the press or release occurred. To add a click action to a ClutterActor: ClutterAction *action = clutter_click_action_new (); clutter_actor_add_action (actor, action); An actor must still be set to reactive so that its signals can be routed to a click action. Create a handler function (note the function signature is different from the one for the press or releas signal handler): void clicked_cb (ClutterClickAction *action, ClutterActor *actor, gpointer user_data) { /* display the number of the clicked button (equivalent * to the number returned by clutter_event_get_button()) */ g_debug ("Button %d clicked", clutter_click_action_get_button (action)); } Connect the signal to the handler: g_signal_connect (action, "clicked", G_CALLBACK (clicked_cb), NULL); The example code gives a bit more detail about how to use click actions.
Full examples Examining properties of a <type>ClutterButtonEvent</type> a code sample should be here... but isn't Using <type>ClutterClickAction</type> to capture button events on an actor a code sample should be here... but isn't Using button and pointer events for drawing This code was inspired by ClutterSmith a code sample should be here... but isn't