You want one actor (the "target") to automatically change its width or height (or both) when the size of another actor (the "source") changes.
Example use cases:
Making an actor adjust itself to the size of the stage (particularly when the stage is resizable).
Putting one actor on top of another and keeping their sizes in sync.
Create a ClutterBindConstraint bound to the width and/or height of one actor (the "source"). Add that constraint to an actor (the "target") whose size should follow the size of the source.
This short example shows how to create and add a constraint;
source
and target
can
be any two ClutterActors:
ClutterConstraint *width_constraint; /* create a constraint which binds a target actor's width to 100px less than * the width of the source actor (use CLUTTER_BIND_HEIGHT to create a * constraint based on an actor's height) * * the third argument is a positive or negative offset from the actor's * dimension, in pixels; this is added to the height or width of the source * actor before the constraint is applied to the target actor */ width_constraint = clutter_bind_constraint_new (source, CLUTTER_BIND_WIDTH, -100); /* add the constraint to an actor */ clutter_actor_add_constraint (target, width_constraint);
Below is a full example, showing how to incorporate a constraint into a Clutter application.
Example 7.3. Constraining the size of a texture to the size of the stage using ClutterBindConstraint
#include <stdlib.h> #include <clutter/clutter.h> static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff }; static const ClutterColor rectangle_color = { 0xaa, 0x99, 0x00, 0xff }; int main (int argc, char *argv[]) { /* the stage is the "source" for constraints on the texture */ ClutterActor *stage; /* the "target" actor which will be bound by the constraints */ ClutterActor *texture; ClutterConstraint *width_binding; ClutterConstraint *height_binding; if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return 1; stage = clutter_stage_new (); clutter_actor_set_size (stage, 400, 400); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); /* make the stage resizable */ clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE); texture = clutter_texture_new (); clutter_actor_set_opacity (texture, 50); clutter_texture_set_repeat (CLUTTER_TEXTURE (texture), TRUE, TRUE); clutter_texture_set_from_file (CLUTTER_TEXTURE (texture), "smiley.png", NULL); /* the texture's width will be 100px less than the stage's */ width_binding = clutter_bind_constraint_new (stage, CLUTTER_BIND_WIDTH, -100); /* the texture's height will be 100px less than the stage's */ height_binding = clutter_bind_constraint_new (stage, CLUTTER_BIND_HEIGHT, -100); /* add the constraints to the texture */ clutter_actor_add_constraint (texture, width_binding); clutter_actor_add_constraint (texture, height_binding); /* add some alignment constraints */ clutter_actor_add_constraint (texture, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5)); clutter_actor_add_constraint (texture, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5)); clutter_container_add_actor (CLUTTER_CONTAINER (stage), texture); clutter_actor_show (stage); clutter_main (); return EXIT_SUCCESS; }
The texture in this example is 100px smaller than the stage, leaving a border of visible stage around the texture; and the texture has a tiled image on it. The tiling changes as the texture changes size. Also note that two ClutterAlignConstraints are added to center the actor on the stage.
The result looks like this:
Sizing constraints are a good solution in these cases:
Where you can't use a layout manager. For example, you can't apply a layout manager to the stage directly; so if you want to control the size of an actor based on the size of the stage (as in the example above), constraints are a good substitute for a layout manager .
Where the layout of a UI is fairly simple (perhaps up to half a dozen actors) and fairly static. An example might be something like a text editor, where the arrangement of the UI (menu bar, toolbar, editing panel, footer) changes infrequently. Of course, it is possible to arrange top-level components using constraints, but still use layout managers inside individual components (e.g. a flow layout manager to manage buttons in the toolbar).
Where you have an actor whose size can change erratically, but you still want to be able to track its size to control another actor's size. An example might be an application like a drawing program, where a user can create their own actors: you might want the user to be able to describe loose, custom constraints between actors like "keep these actors at the same width", then allow those actors to be moved around and resized in a free-form way as a group. In this situation, a layout manager is too rigid and not appropriate; but adding ClutterConstraints to actors in response to user actions could work well.
The sample code in the appendix is the kind of thing you might include in a drawing program: you can resize a texture with a key press (
+
to increase size,-
to decrease), and click on the actor to select/deselect it (a semi-transparent overlay is toggled on the texture). The size of the overlay is bound and aligned to the texture, so that it covers and slightly overlaps the texture regardless of its size.
Note
You can bind an actor to a single dimension (just height or depth) of another actor: you don't have to bind both height and width. Also, you don't have to bind both dimensions of the target to the same source: for example, you could bind the target's height to one source (actor A) and its width to another source (actor B).
A ClutterBindConstraint can also be used to
constrain a target actor's position on the x
and
y
axes to the position of a source actor. This is
covered in another recipe.
There is another way to control the size of a target
actor, based on the size of a source: you can create a handler
for the allocation-changed
signal
of the source, emitted when its size and/or position
changes. This signal includes all the data
about the source's new allocation (height, width, x and y
coordindates), which the handler function can then use to
resize the target.
Alternatively, if you're only interested in
a change to width or height, you can create a handler
for the notify::width
or
notify::height
signal (respectively), and modify
the target's width/height in the handler.
This approach may be useful if you need a type of control over alignment and size which is not possible using constraints alone (e.g. one actor's size should be a proportion of another's). See the code in this section for an example where the size of one actor is dynamically set to 10% more than the size of another.
Note
This recipe explains more about monitoring changes to an actor's size.
Example 7.4. Creating an automatically-resizing overlay for a texture using ClutterBindConstraint
#include <stdlib.h> #include <clutter/clutter.h> #define STAGE_SIDE 400 #define RECTANGLE_SIDE STAGE_SIDE * 0.5 #define TEXTURE_SIZE_MAX STAGE_SIDE * 0.9 #define TEXTURE_SIZE_MIN STAGE_SIDE * 0.1 #define TEXTURE_SIZE_STEP 0.2 #define OVERLAY_OPACITY_OFF 0 #define OVERLAY_OPACITY_ON 100 static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff }; static const ClutterColor overlay_color = { 0xaa, 0x99, 0x00, 0xff }; /* change the texture size with +/- */ static gboolean key_press_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { ClutterActor *texture; gfloat texture_width, texture_height; guint key_pressed; texture = CLUTTER_ACTOR (user_data); clutter_actor_get_size (texture, &texture_width, &texture_height); key_pressed = clutter_event_get_key_symbol (event); if (key_pressed == CLUTTER_KEY_plus) { texture_width *= 1.0 + TEXTURE_SIZE_STEP; texture_height *= 1.0 + TEXTURE_SIZE_STEP; } else if (key_pressed == CLUTTER_KEY_minus) { texture_width *= 1.0 - TEXTURE_SIZE_STEP; texture_height *= 1.0 - TEXTURE_SIZE_STEP; } if (texture_width <= TEXTURE_SIZE_MAX && texture_width >= TEXTURE_SIZE_MIN) clutter_actor_animate (texture, CLUTTER_EASE_OUT_CUBIC, 500, "width", texture_width, "height", texture_height, NULL); return TRUE; } /* turn overlay opacity on/off */ static void click_cb (ClutterClickAction *action, ClutterActor *actor, gpointer user_data) { ClutterActor *overlay = CLUTTER_ACTOR (user_data); guint8 opacity = clutter_actor_get_opacity (overlay); if (opacity < OVERLAY_OPACITY_ON) opacity = OVERLAY_OPACITY_ON; else opacity = OVERLAY_OPACITY_OFF; clutter_actor_set_opacity (overlay, opacity); } int main (int argc, char *argv[]) { ClutterActor *stage; ClutterActor *texture; ClutterActor *overlay; ClutterAction *click; GError *error = NULL; const gchar *filename = "redhand.png"; if (argc > 1) filename = argv[1]; if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return 1; stage = clutter_stage_new (); clutter_actor_set_size (stage, STAGE_SIDE, STAGE_SIDE); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); texture = clutter_texture_new (); clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (texture), TRUE); clutter_actor_set_reactive (texture, TRUE); clutter_actor_set_size (texture, RECTANGLE_SIDE, RECTANGLE_SIDE); clutter_actor_add_constraint (texture, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5)); clutter_actor_add_constraint (texture, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5)); clutter_texture_set_from_file (CLUTTER_TEXTURE (texture), filename, &error); if (error != NULL) { g_warning ("Error loading %s\n%s", filename, error->message); g_error_free (error); exit (EXIT_FAILURE); } /* overlay is 10px wider and taller than the texture, and centered on it; * initially, it is transparent; but it is made semi-opaque when the * texture is clicked */ overlay = clutter_rectangle_new_with_color (&overlay_color); clutter_actor_set_opacity (overlay, OVERLAY_OPACITY_OFF); clutter_actor_add_constraint (overlay, clutter_bind_constraint_new (texture, CLUTTER_BIND_WIDTH, 10)); clutter_actor_add_constraint (overlay, clutter_bind_constraint_new (texture, CLUTTER_BIND_HEIGHT, 10)); clutter_actor_add_constraint (overlay, clutter_align_constraint_new (texture, CLUTTER_ALIGN_X_AXIS, 0.5)); clutter_actor_add_constraint (overlay, clutter_align_constraint_new (texture, CLUTTER_ALIGN_Y_AXIS, 0.5)); click = clutter_click_action_new (); clutter_actor_add_action (texture, click); clutter_container_add (CLUTTER_CONTAINER (stage), texture, overlay, NULL); clutter_actor_raise_top (overlay); g_signal_connect (click, "clicked", G_CALLBACK (click_cb), overlay); g_signal_connect (stage, "key-press-event", G_CALLBACK (key_press_cb), texture); clutter_actor_show (stage); clutter_main (); return EXIT_SUCCESS; }
Example 7.5. Using the allocation-changed
signal of one actor to trigger proportional size changes in
another
#include <stdlib.h> #include <clutter/clutter.h> #define OVERLAY_FACTOR 1.1 static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff }; void allocation_changed_cb (ClutterActor *actor, const ClutterActorBox *allocation, ClutterAllocationFlags flags, gpointer user_data) { ClutterActor *overlay = CLUTTER_ACTOR (user_data); gfloat width, height, x, y; clutter_actor_box_get_size (allocation, &width, &height); clutter_actor_box_get_origin (allocation, &x, &y); clutter_actor_set_size (overlay, width * OVERLAY_FACTOR, height * OVERLAY_FACTOR); clutter_actor_set_position (overlay, x - ((OVERLAY_FACTOR - 1) * width * 0.5), y - ((OVERLAY_FACTOR - 1) * width * 0.5)); } int main (int argc, char *argv[]) { ClutterActor *stage; ClutterActor *actor; ClutterActor *overlay; if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return 1; stage = clutter_stage_new (); clutter_actor_set_size (stage, 400, 400); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); actor = clutter_actor_new (); clutter_actor_set_background_color (actor, CLUTTER_COLOR_Red); clutter_actor_set_size (actor, 100, 100); clutter_actor_set_position (actor, 150, 150); clutter_actor_add_child (stage, actor); overlay = clutter_actor_new (); clutter_actor_set_background_color (overlay, CLUTTER_COLOR_Blue); clutter_actor_set_opacity (overlay, 128); g_signal_connect (actor, "allocation-changed", G_CALLBACK (allocation_changed_cb), overlay); clutter_actor_add_child (stage, overlay); clutter_actor_animate (actor, CLUTTER_LINEAR, 2000, "width", 300.0, "height", 300.0, "x", 50.0, "y", 50.0, NULL); clutter_actor_show (stage); clutter_main (); return EXIT_SUCCESS; }