/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2010 Intel Corporation. * * 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 . * * Author: * Emmanuele Bassi */ /** * ClutterBindConstraint: * * A constraint binding the position or size of an actor * * #ClutterBindConstraint is a [class@Constraint] that binds the * position or the size of the [class@Actor] to which it is applied * to the the position or the size of another [class@Actor], or * "source". * * An offset can be applied to the constraint, to avoid overlapping. The offset * can also be animated. For instance, the following code will set up three * actors to be bound to the same origin: * * ```c * // source * rect[0] = clutter_actor_new (); * clutter_actor_set_background_color (rect[0], &red_color); * clutter_actor_set_position (rect[0], x_pos, y_pos); * clutter_actor_set_size (rect[0], 100, 100); * * // second rectangle * rect[1] = clutter_actor_new (); * clutter_actor_set_background_color (rect[1], &green_color); * clutter_actor_set_size (rect[1], 100, 100); * clutter_actor_set_opacity (rect[1], 0); * * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_X, 0.0); * clutter_actor_add_constraint_with_name (rect[1], "green-x", constraint); * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_Y, 0.0); * clutter_actor_add_constraint_with_name (rect[1], "green-y", constraint); * * // third rectangle * rect[2] = clutter_actor_new (); * clutter_actor_set_background_color (rect[2], &blue_color); * clutter_actor_set_size (rect[2], 100, 100); * clutter_actor_set_opacity (rect[2], 0); * * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_X, 0.0); * clutter_actor_add_constraint_with_name (rect[2], "blue-x", constraint); * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_Y, 0.0); * clutter_actor_add_constraint_with_name (rect[2], "blue-y", constraint); * ``` * * The following code animates the second and third rectangles to "expand" * them horizontally from underneath the first rectangle: * * ```c * clutter_actor_animate (rect[1], CLUTTER_EASE_OUT_CUBIC, 250, * "@constraints.green-x.offset", 100.0, * "opacity", 255, * NULL); * clutter_actor_animate (rect[2], CLUTTER_EASE_OUT_CUBIC, 250, * "@constraints.blue-x.offset", 200.0, * "opacity", 255, * NULL); * ``` */ #include "clutter-build-config.h" #include #include "clutter-bind-constraint.h" #include "clutter-actor-meta-private.h" #include "clutter-actor-private.h" #include "clutter-constraint.h" #include "clutter-debug.h" #include "clutter-enum-types.h" #include "clutter-private.h" #define CLUTTER_BIND_CONSTRAINT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BIND_CONSTRAINT, ClutterBindConstraintClass)) #define CLUTTER_IS_BIND_CONSTRAINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BIND_CONSTRAINT)) #define CLUTTER_BIND_CONSTRAINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BIND_CONSTRAINT, ClutterBindConstraintClass)) struct _ClutterBindConstraint { ClutterConstraint parent_instance; ClutterActor *actor; ClutterActor *source; ClutterBindCoordinate coordinate; gfloat offset; }; struct _ClutterBindConstraintClass { ClutterConstraintClass parent_class; }; enum { PROP_0, PROP_SOURCE, PROP_COORDINATE, PROP_OFFSET, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST]; G_DEFINE_TYPE (ClutterBindConstraint, clutter_bind_constraint, CLUTTER_TYPE_CONSTRAINT); static void source_queue_relayout (ClutterActor *source, ClutterBindConstraint *bind) { if (bind->actor != NULL) _clutter_actor_queue_only_relayout (bind->actor); } static void source_destroyed (ClutterActor *actor, ClutterBindConstraint *bind) { bind->source = NULL; } static void clutter_bind_constraint_update_preferred_size (ClutterConstraint *constraint, ClutterActor *actor, ClutterOrientation direction, float for_size, float *minimum_size, float *natural_size) { ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (constraint); float source_min, source_nat; if (bind->source == NULL) return; /* only these bindings affect the preferred size */ if (!(bind->coordinate == CLUTTER_BIND_WIDTH || bind->coordinate == CLUTTER_BIND_HEIGHT || bind->coordinate == CLUTTER_BIND_SIZE || bind->coordinate == CLUTTER_BIND_ALL)) return; if (clutter_actor_contains (bind->source, actor)) return; switch (direction) { case CLUTTER_ORIENTATION_HORIZONTAL: if (bind->coordinate != CLUTTER_BIND_HEIGHT) { clutter_actor_get_preferred_width (bind->source, for_size, &source_min, &source_nat); *minimum_size = source_min; *natural_size = source_nat; } break; case CLUTTER_ORIENTATION_VERTICAL: if (bind->coordinate != CLUTTER_BIND_WIDTH) { clutter_actor_get_preferred_height (bind->source, for_size, &source_min, &source_nat); *minimum_size = source_min; *natural_size = source_nat; } break; } } static void clutter_bind_constraint_update_allocation (ClutterConstraint *constraint, ClutterActor *actor, ClutterActorBox *allocation) { ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (constraint); gfloat source_width, source_height; gfloat actor_width, actor_height; graphene_point3d_t source_position; source_position = GRAPHENE_POINT3D_INIT (0.f, 0.f, 0.f); if (bind->source == NULL) return; source_position.x = clutter_actor_get_x (bind->source); source_position.y = clutter_actor_get_y (bind->source); clutter_actor_get_size (bind->source, &source_width, &source_height); clutter_actor_box_get_size (allocation, &actor_width, &actor_height); switch (bind->coordinate) { case CLUTTER_BIND_X: allocation->x1 = source_position.x + bind->offset; allocation->x2 = allocation->x1 + actor_width; break; case CLUTTER_BIND_Y: allocation->y1 = source_position.y + bind->offset; allocation->y2 = allocation->y1 + actor_height; break; case CLUTTER_BIND_POSITION: allocation->x1 = source_position.x + bind->offset; allocation->y1 = source_position.y + bind->offset; allocation->x2 = allocation->x1 + actor_width; allocation->y2 = allocation->y1 + actor_height; break; case CLUTTER_BIND_WIDTH: allocation->x2 = allocation->x1 + source_width + bind->offset; break; case CLUTTER_BIND_HEIGHT: allocation->y2 = allocation->y1 + source_height + bind->offset; break; case CLUTTER_BIND_SIZE: allocation->x2 = allocation->x1 + source_width + bind->offset; allocation->y2 = allocation->y1 + source_height + bind->offset; break; case CLUTTER_BIND_ALL: allocation->x1 = source_position.x + bind->offset; allocation->y1 = source_position.y + bind->offset; allocation->x2 = allocation->x1 + source_width + bind->offset; allocation->y2 = allocation->y1 + source_height + bind->offset; break; default: g_assert_not_reached (); break; } clutter_actor_box_clamp_to_pixel (allocation); } static void clutter_bind_constraint_set_actor (ClutterActorMeta *meta, ClutterActor *new_actor) { ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (meta); ClutterActorMetaClass *parent; if (new_actor != NULL && bind->source != NULL && clutter_actor_contains (new_actor, bind->source)) { g_warning (G_STRLOC ": The source actor '%s' is contained " "by the actor '%s' associated to the constraint " "'%s'", _clutter_actor_get_debug_name (bind->source), _clutter_actor_get_debug_name (new_actor), _clutter_actor_meta_get_debug_name (meta)); return; } /* store the pointer to the actor, for later use */ bind->actor = new_actor; parent = CLUTTER_ACTOR_META_CLASS (clutter_bind_constraint_parent_class); parent->set_actor (meta, new_actor); } static void clutter_bind_constraint_dispose (GObject *gobject) { ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject); if (bind->source != NULL) { g_signal_handlers_disconnect_by_func (bind->source, G_CALLBACK (source_destroyed), bind); g_signal_handlers_disconnect_by_func (bind->source, G_CALLBACK (source_queue_relayout), bind); bind->source = NULL; } G_OBJECT_CLASS (clutter_bind_constraint_parent_class)->dispose (gobject); } static void clutter_bind_constraint_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject); switch (prop_id) { case PROP_SOURCE: clutter_bind_constraint_set_source (bind, g_value_get_object (value)); break; case PROP_COORDINATE: clutter_bind_constraint_set_coordinate (bind, g_value_get_enum (value)); break; case PROP_OFFSET: clutter_bind_constraint_set_offset (bind, g_value_get_float (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_bind_constraint_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject); switch (prop_id) { case PROP_SOURCE: g_value_set_object (value, bind->source); break; case PROP_COORDINATE: g_value_set_enum (value, bind->coordinate); break; case PROP_OFFSET: g_value_set_float (value, bind->offset); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_bind_constraint_class_init (ClutterBindConstraintClass *klass) { ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); ClutterConstraintClass *constraint_class = CLUTTER_CONSTRAINT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = clutter_bind_constraint_set_property; gobject_class->get_property = clutter_bind_constraint_get_property; gobject_class->dispose = clutter_bind_constraint_dispose; meta_class->set_actor = clutter_bind_constraint_set_actor; constraint_class->update_allocation = clutter_bind_constraint_update_allocation; constraint_class->update_preferred_size = clutter_bind_constraint_update_preferred_size; /** * ClutterBindConstraint:source: * * The #ClutterActor used as the source for the binding. * * The #ClutterActor must not be contained inside the actor associated * to the constraint. */ obj_props[PROP_SOURCE] = g_param_spec_object ("source", P_("Source"), P_("The source of the binding"), CLUTTER_TYPE_ACTOR, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * ClutterBindConstraint:coordinate: * * The coordinate to be bound */ obj_props[PROP_COORDINATE] = g_param_spec_enum ("coordinate", P_("Coordinate"), P_("The coordinate to bind"), CLUTTER_TYPE_BIND_COORDINATE, CLUTTER_BIND_X, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * ClutterBindConstraint:offset: * * The offset, in pixels, to be applied to the binding */ obj_props[PROP_OFFSET] = g_param_spec_float ("offset", P_("Offset"), P_("The offset in pixels to apply to the binding"), -G_MAXFLOAT, G_MAXFLOAT, 0.0f, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); } static void clutter_bind_constraint_init (ClutterBindConstraint *self) { self->actor = NULL; self->source = NULL; self->coordinate = CLUTTER_BIND_X; self->offset = 0.0f; } /** * clutter_bind_constraint_new: * @source: (allow-none): the #ClutterActor to use as the source of * the binding, or %NULL * @coordinate: the coordinate to bind * @offset: the offset to apply to the binding, in pixels * * Creates a new constraint, binding a #ClutterActor's position to * the given @coordinate of the position of @source * * Return value: the newly created #ClutterBindConstraint */ ClutterConstraint * clutter_bind_constraint_new (ClutterActor *source, ClutterBindCoordinate coordinate, gfloat offset) { g_return_val_if_fail (source == NULL || CLUTTER_IS_ACTOR (source), NULL); return g_object_new (CLUTTER_TYPE_BIND_CONSTRAINT, "source", source, "coordinate", coordinate, "offset", offset, NULL); } /** * clutter_bind_constraint_set_source: * @constraint: a #ClutterBindConstraint * @source: (allow-none): a #ClutterActor, or %NULL to unset the source * * Sets the source #ClutterActor for the constraint */ void clutter_bind_constraint_set_source (ClutterBindConstraint *constraint, ClutterActor *source) { ClutterActor *old_source, *actor; ClutterActorMeta *meta; g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint)); g_return_if_fail (source == NULL || CLUTTER_IS_ACTOR (source)); if (constraint->source == source) return; meta = CLUTTER_ACTOR_META (constraint); actor = clutter_actor_meta_get_actor (meta); if (source != NULL && actor != NULL) { if (clutter_actor_contains (actor, source)) { g_warning (G_STRLOC ": The source actor '%s' is contained " "by the actor '%s' associated to the constraint " "'%s'", _clutter_actor_get_debug_name (source), _clutter_actor_get_debug_name (actor), _clutter_actor_meta_get_debug_name (meta)); return; } } old_source = constraint->source; if (old_source != NULL) { g_signal_handlers_disconnect_by_func (old_source, G_CALLBACK (source_destroyed), constraint); g_signal_handlers_disconnect_by_func (old_source, G_CALLBACK (source_queue_relayout), constraint); } constraint->source = source; if (constraint->source != NULL) { g_signal_connect (constraint->source, "queue-relayout", G_CALLBACK (source_queue_relayout), constraint); g_signal_connect (constraint->source, "destroy", G_CALLBACK (source_destroyed), constraint); if (constraint->actor != NULL) clutter_actor_queue_relayout (constraint->actor); } g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_SOURCE]); } /** * clutter_bind_constraint_get_source: * @constraint: a #ClutterBindConstraint * * Retrieves the #ClutterActor set using clutter_bind_constraint_set_source() * * Return value: (transfer none): a pointer to the source actor */ ClutterActor * clutter_bind_constraint_get_source (ClutterBindConstraint *constraint) { g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint), NULL); return constraint->source; } /** * clutter_bind_constraint_set_coordinate: * @constraint: a #ClutterBindConstraint * @coordinate: the coordinate to bind * * Sets the coordinate to bind in the constraint */ void clutter_bind_constraint_set_coordinate (ClutterBindConstraint *constraint, ClutterBindCoordinate coordinate) { g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint)); if (constraint->coordinate == coordinate) return; constraint->coordinate = coordinate; if (constraint->actor != NULL) clutter_actor_queue_relayout (constraint->actor); g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_COORDINATE]); } /** * clutter_bind_constraint_get_coordinate: * @constraint: a #ClutterBindConstraint * * Retrieves the bound coordinate of the constraint * * Return value: the bound coordinate */ ClutterBindCoordinate clutter_bind_constraint_get_coordinate (ClutterBindConstraint *constraint) { g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint), CLUTTER_BIND_X); return constraint->coordinate; } /** * clutter_bind_constraint_set_offset: * @constraint: a #ClutterBindConstraint * @offset: the offset to apply, in pixels * * Sets the offset to be applied to the constraint */ void clutter_bind_constraint_set_offset (ClutterBindConstraint *constraint, gfloat offset) { g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint)); if (fabs (constraint->offset - offset) < 0.00001f) return; constraint->offset = offset; if (constraint->actor != NULL) clutter_actor_queue_relayout (constraint->actor); g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_OFFSET]); } /** * clutter_bind_constraint_get_offset: * @constraint: a #ClutterBindConstraint * * Retrieves the offset set using clutter_bind_constraint_set_offset() * * Return value: the offset, in pixels */ gfloat clutter_bind_constraint_get_offset (ClutterBindConstraint *bind) { g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (bind), 0.0); return bind->offset; }