/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ #include "st-private.h" /** * _st_actor_get_preferred_width: * @actor: a #ClutterActor * @for_height: as with clutter_actor_get_preferred_width() * @y_fill: %TRUE if @actor will fill its allocation vertically * @min_width_p: as with clutter_actor_get_preferred_width() * @natural_width_p: as with clutter_actor_get_preferred_width() * * Like clutter_actor_get_preferred_width(), but if @y_fill is %FALSE, * then it will compute a width request based on the assumption that * @actor will be given an allocation no taller than its natural * height. */ void _st_actor_get_preferred_width (ClutterActor *actor, gfloat for_height, gboolean y_fill, gfloat *min_width_p, gfloat *natural_width_p) { if (!y_fill && for_height != -1) { ClutterRequestMode mode; gfloat natural_height; g_object_get (G_OBJECT (actor), "request-mode", &mode, NULL); if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT) { clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height); if (for_height > natural_height) for_height = natural_height; } } clutter_actor_get_preferred_width (actor, for_height, min_width_p, natural_width_p); } /** * _st_actor_get_preferred_height: * @actor: a #ClutterActor * @for_width: as with clutter_actor_get_preferred_height() * @x_fill: %TRUE if @actor will fill its allocation horizontally * @min_height_p: as with clutter_actor_get_preferred_height() * @natural_height_p: as with clutter_actor_get_preferred_height() * * Like clutter_actor_get_preferred_height(), but if @x_fill is * %FALSE, then it will compute a height request based on the * assumption that @actor will be given an allocation no wider than * its natural width. */ void _st_actor_get_preferred_height (ClutterActor *actor, gfloat for_width, gboolean x_fill, gfloat *min_height_p, gfloat *natural_height_p) { if (!x_fill && for_width != -1) { ClutterRequestMode mode; gfloat natural_width; g_object_get (G_OBJECT (actor), "request-mode", &mode, NULL); if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width); if (for_width > natural_width) for_width = natural_width; } } clutter_actor_get_preferred_height (actor, for_width, min_height_p, natural_height_p); } /** * _st_allocate_fill: * @parent: the parent #StWidget * @child: the child (not necessarily an #StWidget) * @childbox: total space that could be allocated to @child * @x_alignment: horizontal alignment within @childbox * @y_alignment: vertical alignment within @childbox * @x_fill: whether or not to fill @childbox horizontally * @y_fill: whether or not to fill @childbox vertically * * Given @childbox, containing the initial allocation of @child, this * adjusts the horizontal allocation if @x_fill is %FALSE, and the * vertical allocation if @y_fill is %FALSE, by: * * - reducing the allocation if it is larger than @child's natural * size. * * - adjusting the position of the child within the allocation * according to @x_alignment/@y_alignment (and flipping * @x_alignment if @parent has %ST_TEXT_DIRECTION_RTL) * * If @x_fill and @y_fill are both %TRUE, or if @child's natural size * is larger than the initial allocation in @childbox, then @childbox * will be unchanged. * * If you are allocating children with _st_allocate_fill(), you should * determine their preferred sizes using * _st_actor_get_preferred_width() and * _st_actor_get_preferred_height(), not with the corresponding * Clutter methods. */ void _st_allocate_fill (StWidget *parent, ClutterActor *child, ClutterActorBox *childbox, StAlign x_alignment, StAlign y_alignment, gboolean x_fill, gboolean y_fill) { gfloat natural_width, natural_height; gfloat min_width, min_height; gfloat child_width, child_height; gfloat available_width, available_height; ClutterRequestMode request; gdouble x_align, y_align; available_width = childbox->x2 - childbox->x1; available_height = childbox->y2 - childbox->y1; if (available_width < 0) { available_width = 0; childbox->x2 = childbox->x1; } if (available_height < 0) { available_height = 0; childbox->y2 = childbox->y1; } /* If we are filling both horizontally and vertically then we don't * need to do anything else. */ if (x_fill && y_fill) return; _st_get_align_factors (parent, x_alignment, y_alignment, &x_align, &y_align); /* The following is based on clutter_actor_get_preferred_size(), but * modified to cope with the fact that the available size may be * less than the preferred size. */ request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH; g_object_get (G_OBJECT (child), "request-mode", &request, NULL); if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { clutter_actor_get_preferred_width (child, available_height, &min_width, &natural_width); child_width = CLAMP (natural_width, min_width, available_width); clutter_actor_get_preferred_height (child, child_width, &min_height, &natural_height); child_height = CLAMP (natural_height, min_height, available_height); } else { clutter_actor_get_preferred_height (child, available_width, &min_height, &natural_height); child_height = CLAMP (natural_height, min_height, available_height); clutter_actor_get_preferred_width (child, child_height, &min_width, &natural_width); child_width = CLAMP (natural_width, min_width, available_width); } if (!x_fill) { childbox->x1 += (int)((available_width - child_width) * x_align); childbox->x2 = childbox->x1 + (int) child_width; } if (!y_fill) { childbox->y1 += (int)((available_height - child_height) * y_align); childbox->y2 = childbox->y1 + (int) child_height; } } /** * _st_get_align_factors: * @widget: an #StWidget * @x_align: an #StAlign * @y_align: an #StAlign * @x_align_out: (out) (allow-none): @x_align as a #gdouble * @y_align_out: (out) (allow-none): @y_align as a #gdouble * * Converts @x_align and @y_align to #gdouble values. If @widget has * %ST_TEXT_DIRECTION_RTL, the @x_align_out value will be flipped * relative to @x_align. */ void _st_get_align_factors (StWidget *widget, StAlign x_align, StAlign y_align, gdouble *x_align_out, gdouble *y_align_out) { if (x_align_out) { switch (x_align) { case ST_ALIGN_START: *x_align_out = 0.0; break; case ST_ALIGN_MIDDLE: *x_align_out = 0.5; break; case ST_ALIGN_END: *x_align_out = 1.0; break; default: g_warn_if_reached (); break; } if (st_widget_get_direction (widget) == ST_TEXT_DIRECTION_RTL) *x_align_out = 1.0 - *x_align_out; } if (y_align_out) { switch (y_align) { case ST_ALIGN_START: *y_align_out = 0.0; break; case ST_ALIGN_MIDDLE: *y_align_out = 0.5; break; case ST_ALIGN_END: *y_align_out = 1.0; break; default: g_warn_if_reached (); break; } } } /** * _st_set_text_from_style: * @text: Target #ClutterText * @theme_node: Source #StThemeNode * * Set various GObject properties of the @text object using * CSS information from @theme_node. */ void _st_set_text_from_style (ClutterText *text, StThemeNode *theme_node) { ClutterColor color; StTextDecoration decoration; PangoAttrList *attribs; const PangoFontDescription *font; gchar *font_string; st_theme_node_get_foreground_color (theme_node, &color); clutter_text_set_color (text, &color); font = st_theme_node_get_font (theme_node); font_string = pango_font_description_to_string (font); clutter_text_set_font_name (text, font_string); g_free (font_string); attribs = pango_attr_list_new (); decoration = st_theme_node_get_text_decoration (theme_node); if (decoration & ST_TEXT_DECORATION_UNDERLINE) { PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); pango_attr_list_insert (attribs, underline); } if (decoration & ST_TEXT_DECORATION_LINE_THROUGH) { PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE); pango_attr_list_insert (attribs, strikethrough); } /* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately * skip BLINK (for now...) */ clutter_text_set_attributes (text, attribs); pango_attr_list_unref (attribs); } /** * _st_container_add_actor: * @container: a #ClutterContainer * @actor: a #ClutterActor * @children: pointer to @container's list of children * * A basic implementation for clutter_container_add_actor(). * Mostly copied from clutter_group_real_add(). */ void _st_container_add_actor (ClutterContainer *container, ClutterActor *actor, GList **children) { g_object_ref (actor); *children = g_list_append (*children, actor); clutter_actor_set_parent (actor, CLUTTER_ACTOR (container)); /* queue a relayout, to get the correct positioning inside * the ::actor-added signal handlers */ clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); g_signal_emit_by_name (container, "actor-added", actor); clutter_container_sort_depth_order (container); g_object_unref (actor); } /** * _st_container_remove_actor: * @container: a #ClutterContainer * @actor: a #ClutterActor * @children: pointer to @container's list of children * * A basic implementation for clutter_container_remove_actor(). * Mostly copied from clutter_group_real_remove(). */ void _st_container_remove_actor (ClutterContainer *container, ClutterActor *actor, GList **children) { g_object_ref (actor); *children = g_list_remove (*children, actor); clutter_actor_unparent (actor); /* queue a relayout, to get the correct positioning inside * the ::actor-removed signal handlers */ clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); /* at this point, the actor passed to the "actor-removed" signal * handlers is not parented anymore to the container but since we * are holding a reference on it, it's still valid */ g_signal_emit_by_name (container, "actor-removed", actor); if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); g_object_unref (actor); } /** * _st_container_raise: * @container: a #ClutterContainer * @callback: callback * @user_data: data for @callback * @children: pointer to @container's list of children * * A basic implementation for clutter_container_foreach(). * Mostly copied from clutter_group_real_foreach(). */ void _st_container_foreach (ClutterContainer *container, ClutterCallback callback, gpointer user_data, GList **children) { GList *l; for (l = *children; l; l = l->next) (* callback) (CLUTTER_ACTOR (l->data), user_data); } /** * _st_container_raise: * @container: a #ClutterContainer * @actor: a #ClutterActor to raise * @sibling: the sibling to raise to, or %NULL to put on top * @children: pointer to @container's list of children * * A basic implementation for clutter_container_raise(). * Mostly copied from clutter_group_real_raise(). */ void _st_container_raise (ClutterContainer *container, ClutterActor *actor, ClutterActor *sibling, GList **children) { *children = g_list_remove (*children, actor); /* Raise at the top */ if (!sibling) { GList *last_item; last_item = g_list_last (*children); if (last_item) sibling = last_item->data; *children = g_list_append (*children, actor); } else { gint pos; pos = g_list_index (*children, sibling) + 1; *children = g_list_insert (*children, actor, pos); } /* set Z ordering a value below, this will then call sort * as values are equal ordering shouldn't change but Z * values will be correct. * * FIXME: optimise */ if (sibling && clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor)) { clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling)); } if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); } /** * _st_container_lower: * @container: a #ClutterContainer * @actor: a #ClutterActor to lower * @sibling: the sibling to lower to, or %NULL to put on bottom * @children: pointer to @container's list of children * * A basic implementation for clutter_container_lower(). * Mostly copied from clutter_group_real_lower(). */ void _st_container_lower (ClutterContainer *container, ClutterActor *actor, ClutterActor *sibling, GList **children) { *children = g_list_remove (*children, actor); /* Push to bottom */ if (!sibling) { GList *last_item; last_item = g_list_first (*children); if (last_item) sibling = last_item->data; *children = g_list_prepend (*children, actor); } else { gint pos; pos = g_list_index (*children, sibling); *children = g_list_insert (*children, actor, pos); } /* See comment in _st_container_raise() for this */ if (sibling && clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor)) { clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling)); } if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); } static gint sort_z_order (gconstpointer a, gconstpointer b) { float depth_a, depth_b; depth_a = clutter_actor_get_depth (CLUTTER_ACTOR (a)); depth_b = clutter_actor_get_depth (CLUTTER_ACTOR (b)); if (depth_a < depth_b) return -1; if (depth_a > depth_b) return 1; return 0; } /** * _st_container_sort_depth_order: * @container: a #ClutterContainer * @children: pointer to @container's list of children * * A basic implementation for clutter_container_sort_depth_order(). * Mostly copied from clutter_group_real_sort_depth_order(). */ void _st_container_sort_depth_order (ClutterContainer *container, GList **children) { *children = g_list_sort (*children, sort_z_order); if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); }