gnome-shell/src/st/st-private.c

541 lines
16 KiB
C

/* -*- 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));
}