#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "cogl.h" #include "clutter-box.h" #include "clutter-container.h" #include "clutter-debug.h" #include "clutter-enum-types.h" #include "clutter-main.h" #include "clutter-private.h" /** * SECTION:clutter-box * @short_description: Base class for layout containers * * #ClutterBox is a base class for containers which impose a specific layout * on their children, unlike #ClutterGroup which is a free-form container. * * Layout containers are expected to move and size their children depending * on a layout contract they establish per-class. For instance, a #ClutterHBox * (a subclass of #ClutterBox) lays out its children along an imaginary * horizontal line. * * All #ClutterBoxes have a margin, which is decomposed in four * components (top, right, bottom left) and a background color. Each child * of a #ClutterBox has a packing type and a padding, decomposed like the * margin. Actors can be packed using clutter_box_pack() and providing * the packing type and the padding, or using clutter_box_pack_defaults() * and setting a default padding with clutter_box_set_default_padding(). * A #ClutterBox implements the #ClutterContainer interface: calling * clutter_container_add_actor() on a #ClutterBox will automatically invoke * clutter_box_pack_defaults(). * * Each child of a #ClutterBox has its packing information wrapped into the * #ClutterBoxChild structure, which can be retrieved either using the * clutter_box_query_child() or the clutter_box_query_nth_child() function. * * Subclasses of #ClutterBox must implement the ClutterBox::pack_child and * ClutterBox::unpack_child virtual functions; these functions will be called * when adding a child and when removing one, respectively. * * #ClutterBox is available since Clutter 0.4 */ enum { PROP_0, PROP_MARGIN, PROP_COLOR }; static void clutter_container_iface_init (ClutterContainerIface *iface); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ClutterBox, clutter_box, CLUTTER_TYPE_ACTOR, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, clutter_container_iface_init)); static void clutter_box_add (ClutterContainer *container, ClutterActor *actor) { clutter_box_pack_defaults (CLUTTER_BOX (container), actor); } static void clutter_box_remove (ClutterContainer *container, ClutterActor *actor) { ClutterBox *box = CLUTTER_BOX (container); GList *l; g_object_ref (actor); for (l = box->children; l; l = l->next) { ClutterBoxChild *child = l->data; if (child->actor == actor) { CLUTTER_BOX_GET_CLASS (box)->unpack_child (box, child); clutter_actor_unparent (actor); box->children = g_list_remove_link (box->children, l); g_list_free (l); g_slice_free (ClutterBoxChild, child); g_signal_emit_by_name (container, "actor-removed", actor); if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (box))) clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); break; } } g_object_unref (actor); } static void clutter_box_foreach (ClutterContainer *container, ClutterCallback callback, gpointer user_data) { ClutterBox *box = CLUTTER_BOX (container); GList *l; for (l = box->children; l; l = l->next) { ClutterBoxChild *child = l->data; if (child->pack_type == CLUTTER_PACK_START) (* callback) (child->actor, user_data); } for (l = g_list_last (box->children); l; l = l->prev) { ClutterBoxChild *child = l->data; if (child->pack_type == CLUTTER_PACK_END) (* callback) (child->actor, user_data); } } static void clutter_box_raise (ClutterContainer *container, ClutterActor *actor, ClutterActor *sibling) { ClutterBox *box = CLUTTER_BOX (container); ClutterBoxChild *child = NULL, *sibling_child = NULL; GList *l; gint pos; for (l = box->children; l; l = l->next) { child = l->data; if (child->actor == actor) break; } box->children = g_list_remove (box->children, child); if (!sibling) { GList *last_item; /* raise to top */ last_item = g_list_last (box->children); if (last_item) sibling_child = last_item->data; box->children = g_list_append (box->children, child); } else { for (pos = 1, l = box->children; l; l = l->next, pos += 1) { sibling_child = l->data; if (sibling_child->actor == sibling) break; } box->children = g_list_insert (box->children, child, pos); } if (sibling_child) { ClutterActor *a = child->actor; ClutterActor *b = sibling_child->actor; if (clutter_actor_get_depth (a) != clutter_actor_get_depth (b)) clutter_actor_set_depth (a, clutter_actor_get_depth (b)); } } static void clutter_box_lower (ClutterContainer *container, ClutterActor *actor, ClutterActor *sibling) { ClutterBox *box = CLUTTER_BOX (container); ClutterBoxChild *child = NULL, *sibling_child = NULL; GList *l; gint pos; for (l = box->children; l; l = l->next) { child = l->data; if (child->actor == actor) break; } box->children = g_list_remove (box->children, child); if (!sibling) { GList *first_item; /* lower to bottom */ first_item = g_list_first (box->children); if (first_item) sibling_child = first_item->data; box->children = g_list_prepend (box->children, child); } else { for (pos = 1, l = box->children; l; l = l->next, pos += 1) { sibling_child = l->data; if (sibling_child->actor == sibling) break; } box->children = g_list_insert (box->children, child, pos); } if (sibling_child) { ClutterActor *a = child->actor; ClutterActor *b = sibling_child->actor; if (clutter_actor_get_depth (a) != clutter_actor_get_depth (b)) clutter_actor_set_depth (a, clutter_actor_get_depth (b)); } } static gint sort_z_order (gconstpointer a, gconstpointer b) { ClutterBoxChild *child_a = (ClutterBoxChild *) a; ClutterBoxChild *child_b = (ClutterBoxChild *) b; gint depth_a, depth_b; depth_a = clutter_actor_get_depth (child_a->actor); depth_b = clutter_actor_get_depth (child_b->actor); if (depth_a == depth_b) return 0; if (depth_a > depth_b) return 1; return -1; } static void clutter_box_sort_depth_order (ClutterContainer *container) { ClutterBox *box = CLUTTER_BOX (container); box->children = g_list_sort (box->children, sort_z_order); if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (box))) clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); } static void clutter_container_iface_init (ClutterContainerIface *iface) { iface->add = clutter_box_add; iface->remove = clutter_box_remove; iface->foreach = clutter_box_foreach; iface->raise = clutter_box_raise; iface->lower = clutter_box_lower; iface->sort_depth_order = clutter_box_sort_depth_order; } static void clutter_box_show_all (ClutterActor *actor) { ClutterBox *box = CLUTTER_BOX (actor); GList *l; for (l = box->children; l; l = l->next) { ClutterBoxChild *child = l->data; clutter_actor_show (child->actor); } clutter_actor_show (actor); } static void clutter_box_hide_all (ClutterActor *actor) { ClutterBox *box = CLUTTER_BOX (actor); GList *l; clutter_actor_hide (actor); for (l = box->children; l; l = l->next) { ClutterBoxChild *child = l->data; clutter_actor_hide (child->actor); } } static void clutter_box_paint (ClutterActor *actor) { ClutterBox *box = CLUTTER_BOX (actor); GList *l; cogl_push_matrix (); cogl_color (&box->color); for (l = box->children; l; l = l->next) { ClutterBoxChild *child = l->data; if (CLUTTER_ACTOR_IS_MAPPED (child->actor)) clutter_actor_paint (child->actor); } cogl_pop_matrix (); } static void clutter_box_pick (ClutterActor *actor, const ClutterColor *color) { /* just repaint; in the future we might enter in a "focused" status here */ clutter_box_paint (actor); } static void clutter_box_dispose (GObject *gobject) { ClutterBox *box = CLUTTER_BOX (gobject); GList *l; for (l = box->children; l; l = l->next) { ClutterBoxChild *child = l->data; clutter_actor_destroy (child->actor); g_slice_free (ClutterBoxChild, child); } g_list_free (box->children); box->children = NULL; G_OBJECT_CLASS (clutter_box_parent_class)->dispose (gobject); } static void clutter_box_pack_child_unimplemented (ClutterBox *box, ClutterBoxChild *child) { g_warning ("ClutterBox of type `%s' does not implement the " "ClutterBox::pack_child method.", g_type_name (G_OBJECT_TYPE (box))); } static void clutter_box_unpack_child_unimplemented (ClutterBox *box, ClutterBoxChild *child) { g_warning ("ClutterBox of type `%s' does not implement the " "ClutterBox::unpack_child method.", g_type_name (G_OBJECT_TYPE (box))); } static void clutter_box_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterBox *box = CLUTTER_BOX (gobject); switch (prop_id) { case PROP_COLOR: clutter_box_set_color (box, g_value_get_boxed (value)); break; case PROP_MARGIN: clutter_box_set_margin (box, g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_box_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterBox *box = CLUTTER_BOX (gobject); switch (prop_id) { case PROP_MARGIN: { ClutterMargin margin; clutter_box_get_margin (box, &margin); g_value_set_boxed (value, &margin); } break; case PROP_COLOR: { ClutterColor color; clutter_box_get_color (box, &color); g_value_set_boxed (value, &color); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_box_class_init (ClutterBoxClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); gobject_class->set_property = clutter_box_set_property; gobject_class->get_property = clutter_box_get_property; gobject_class->dispose = clutter_box_dispose; actor_class->show_all = clutter_box_show_all; actor_class->hide_all = clutter_box_hide_all; actor_class->paint = clutter_box_paint; actor_class->pick = clutter_box_pick; klass->pack_child = clutter_box_pack_child_unimplemented; klass->unpack_child = clutter_box_unpack_child_unimplemented; /** * ClutterBox:margin: * * The margin between the inner border of a #ClutterBox and its * children. * * Since: 0.4 */ g_object_class_install_property (gobject_class, PROP_MARGIN, g_param_spec_boxed ("margin", "Margin", "Margin between the inner border of a box and its children", CLUTTER_TYPE_MARGIN, CLUTTER_PARAM_READWRITE)); /** * ClutterBox:color: * * The background color of a #ClutterBox. * * Since: 0.4 */ g_object_class_install_property (gobject_class, PROP_COLOR, g_param_spec_boxed ("color", "Color", "Background color of a box", CLUTTER_TYPE_COLOR, CLUTTER_PARAM_READWRITE)); } static void clutter_box_init (ClutterBox *box) { box->allocation.x1 = box->allocation.y1 = 0; box->allocation.x2 = box->allocation.y2 = -1; } /* * Public API */ /** * clutter_box_pack: * @box: a #ClutterBox * @actor: a #ClutterActor to pack into the box * @pack_type: Type of packing to use * @padding: padding to use on the actor * * Packs @actor into @box. * * Since: 0.4 */ void clutter_box_pack (ClutterBox *box, ClutterActor *actor, ClutterPackType pack_type, const ClutterPadding *padding) { ClutterBoxChild *child; g_return_if_fail (CLUTTER_IS_BOX (box)); g_return_if_fail (CLUTTER_IS_ACTOR (actor)); g_return_if_fail (padding != NULL); child = g_slice_new (ClutterBoxChild); child->actor = actor; child->pack_type = pack_type; memcpy (&(child->padding), padding, sizeof (ClutterPadding)); CLUTTER_BOX_GET_CLASS (box)->pack_child (box, child); box->children = g_list_prepend (box->children, child); clutter_actor_set_parent (actor, CLUTTER_ACTOR (box)); if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (box))) clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); } /** * clutter_box_pack_defaults: * @box: a #ClutterBox * @actor: a #ClutterActor * * Packs @actor into @box, using the default settings for the * pack type and padding. * * Since: 0.4 */ void clutter_box_pack_defaults (ClutterBox *box, ClutterActor *actor) { g_return_if_fail (CLUTTER_IS_BOX (box)); g_return_if_fail (CLUTTER_IS_ACTOR (actor)); clutter_box_pack (box, actor, CLUTTER_PACK_START, &box->default_padding); } /** * clutter_box_query_child: * @box: a #ClutterBox * @actor: child to query * @child: return location for a #ClutterBoxChild or %NULL * * Queries @box for the packing data of @actor. * * Return value: %TRUE if @actor is a child of @box * * Since: 0.4 */ gboolean clutter_box_query_child (ClutterBox *box, ClutterActor *actor, ClutterBoxChild *child) { GList *l; g_return_val_if_fail (CLUTTER_IS_BOX (box), FALSE); g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE); for (l = box->children; l; l = l->next) { ClutterBoxChild *box_child = l->data; if (box_child->actor == actor) { if (child) { child->actor = actor; child->pack_type = box_child->pack_type; child->child_coords.x1 = box_child->child_coords.x1; child->child_coords.y1 = box_child->child_coords.y1; child->child_coords.x2 = box_child->child_coords.x2; child->child_coords.y2 = box_child->child_coords.y2; child->padding.top = box_child->padding.top; child->padding.right = box_child->padding.right; child->padding.bottom = box_child->padding.bottom; child->padding.left = box_child->padding.left; } return TRUE; } } return FALSE; } /** * clutter_box_query_nth_child: * @box: a #ClutterBox * @index_: position of the child * @child: return value for a #ClutterBoxChild, or %NULL * * Queries the child of @box at @index_ and puts the packing informations * inside @child. * * Return value: %TRUE if an actor was found at @index_ * * Since: 0.4 */ gboolean clutter_box_query_nth_child (ClutterBox *box, gint index_, ClutterBoxChild *child) { ClutterBoxChild *box_child; g_return_val_if_fail (CLUTTER_IS_BOX (box), FALSE); g_return_val_if_fail (index_ > 0, FALSE); box_child = g_list_nth_data (box->children, index_); if (!box_child) return FALSE; if (child) { child->actor = box_child->actor; child->pack_type = box_child->pack_type; child->child_coords.x1 = box_child->child_coords.x1; child->child_coords.y1 = box_child->child_coords.y1; child->child_coords.x2 = box_child->child_coords.x2; child->child_coords.y2 = box_child->child_coords.y2; child->padding.top = box_child->padding.top; child->padding.right = box_child->padding.right; child->padding.bottom = box_child->padding.bottom; child->padding.left = box_child->padding.left; } return TRUE; } /** * clutter_box_get_margin: * @box: a #ClutterBox * @margin: return location for a #ClutterMargin * * Gets the value set using clutter_box_set_margin(). * * Since: 0.4 */ void clutter_box_get_margin (ClutterBox *box, ClutterMargin *margin) { g_return_if_fail (CLUTTER_IS_BOX (box)); g_return_if_fail (margin != NULL); margin->top = box->margin.top; margin->right = box->margin.right; margin->bottom = box->margin.bottom; margin->left = box->margin.left; } /** * clutter_box_set_margin: * @box: a #ClutterBox * @margin: a #ClutterMargin, or %NULL to unset the margin * * Sets the margin, in #ClutterUnits, between the inner border * of the box and the children of the box. * * Since: 0.4 */ void clutter_box_set_margin (ClutterBox *box, const ClutterMargin *margin) { g_return_if_fail (CLUTTER_IS_BOX (box)); if (margin) { box->margin.top = margin->top; box->margin.right = margin->right; box->margin.bottom = margin->bottom; box->margin.left = margin->left; } else { box->margin.top = 0; box->margin.right = 0; box->margin.bottom = 0; box->margin.left = 0; } if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (box))) clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); g_object_notify (G_OBJECT (box), "margin"); } /** * clutter_box_get_color: * @box: a #ClutterBox * @color: return location for the color * * Gets the background color of the box set with clutter_box_set_color(). * * Since: 0.4 */ void clutter_box_get_color (ClutterBox *box, ClutterColor *color) { g_return_if_fail (CLUTTER_IS_BOX (box)); g_return_if_fail (color != NULL); color->red = box->color.red; color->green = box->color.green; color->blue = box->color.blue; color->alpha = box->color.alpha; } /** * clutter_box_set_color: * @box: a #ClutterBox * @color: the background color of the box * * Sets the background color of the box. * * Since: 0.4 */ void clutter_box_set_color (ClutterBox *box, const ClutterColor *color) { g_return_if_fail (CLUTTER_IS_BOX (box)); g_return_if_fail (color != NULL); box->color.red = color->red; box->color.green = color->green; box->color.blue = color->blue; box->color.alpha = color->alpha; if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (box))) clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); g_object_notify (G_OBJECT (box), "color"); } /** * clutter_box_remove_all: * @box: a #ClutterBox * * Removes all children actors from the #ClutterBox * * Since: 0.4 */ void clutter_box_remove_all (ClutterBox *box) { GList *children; g_return_if_fail (CLUTTER_IS_BOX (box)); children = box->children; while (children) { ClutterBoxChild *child = children->data; children = children->next; clutter_container_remove_actor (CLUTTER_CONTAINER (box), child->actor); } } /** * clutter_box_set_default_padding: * @box: a #ClutterBox * @padding_top: top padding, in pixels * @padding_right: right padding, in pixels * @padding_bottom: bottom padding, in pixels * @padding_left: left padding, in pixels * * Sets the default padding for children, which will be used when * packing actors with clutter_box_pack_defaults(). The padding is * given in pixels. * * Since: 0.4 */ void clutter_box_set_default_padding (ClutterBox *box, gint padding_top, gint padding_right, gint padding_bottom, gint padding_left) { g_return_if_fail (CLUTTER_IS_BOX (box)); box->default_padding.top = CLUTTER_UNITS_FROM_INT (padding_top); box->default_padding.right = CLUTTER_UNITS_FROM_INT (padding_right); box->default_padding.bottom = CLUTTER_UNITS_FROM_INT (padding_bottom); box->default_padding.left = CLUTTER_UNITS_FROM_INT (padding_left); } /** * clutter_box_get_default_padding: * @box: a #ClutterBox * @padding_top: return location for the top padding, or %NULL * @padding_right: return location for the right padding, or %NULL * @padding_bottom: return location for the bottom padding, or %NULL * @padding_left: return location for the left padding, or %NULL * * Gets the default padding set with clutter_box_set_default_padding(). * * Since: 0.4 */ void clutter_box_get_default_padding (ClutterBox *box, gint *padding_top, gint *padding_right, gint *padding_bottom, gint *padding_left) { g_return_if_fail (CLUTTER_IS_BOX (box)); if (padding_top) *padding_top = CLUTTER_UNITS_TO_INT (box->default_padding.top); if (padding_right) *padding_right = CLUTTER_UNITS_TO_INT (box->default_padding.right); if (padding_bottom) *padding_bottom = CLUTTER_UNITS_TO_INT (box->default_padding.bottom); if (padding_left) *padding_left = CLUTTER_UNITS_TO_INT (box->default_padding.left); } /* * Boxed types */ static void clutter_margin_free (ClutterMargin *margin) { if (G_LIKELY (margin)) { g_slice_free (ClutterMargin, margin); } } static ClutterMargin * clutter_margin_copy (const ClutterMargin *margin) { ClutterMargin *copy; g_return_val_if_fail (margin != NULL, NULL); copy = g_slice_new (ClutterMargin); *copy = *margin; return copy; } GType clutter_margin_get_type (void) { static GType gtype = 0; if (G_UNLIKELY (gtype == 0)) gtype = g_boxed_type_register_static (g_intern_static_string ("ClutterMargin"), (GBoxedCopyFunc) clutter_margin_copy, (GBoxedFreeFunc) clutter_margin_free); return gtype; } static void clutter_padding_free (ClutterPadding *padding) { if (G_LIKELY (padding)) { g_slice_free (ClutterPadding, padding); } } static ClutterPadding * clutter_padding_copy (const ClutterPadding *padding) { ClutterPadding *copy; g_return_val_if_fail (padding != NULL, NULL); copy = g_slice_new (ClutterPadding); *copy = *padding; return copy; } GType clutter_padding_get_type (void) { static GType gtype = 0; if (G_UNLIKELY (gtype == 0)) gtype = g_boxed_type_register_static (g_intern_static_string ("ClutterPadding"), (GBoxedCopyFunc) clutter_padding_copy, (GBoxedFreeFunc) clutter_padding_free); return gtype; }