From 2dfe113a4251ebfd89931dcaff45374748958193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sat, 21 Nov 2009 04:19:56 +0100 Subject: [PATCH] Implement -st-shadow for StWidget Add support for a new -st-shadow property, which is based loosely on the CSS3 box-shadow property: http://www.css3.info/preview/box-shadow/ It defers from the specification as follows: * no multiple shadows * the optional color argument may be placed anywhere * the shape is not determined by the widget's bounding box, but by the background-image property https://bugzilla.gnome.org/show_bug.cgi?id=603691 --- src/Makefile-st.am | 4 + src/st/st-shadow-texture.c | 274 +++++++++++++++++++++++++++++++++++++ src/st/st-shadow-texture.h | 30 ++++ src/st/st-shadow.c | 89 ++++++++++++ src/st/st-shadow.h | 42 ++++++ src/st/st-theme-node.c | 89 ++++++++++++ src/st/st-theme-node.h | 2 + src/st/st-widget.c | 86 +++++++++++- 8 files changed, 614 insertions(+), 2 deletions(-) create mode 100644 src/st/st-shadow-texture.c create mode 100644 src/st/st-shadow-texture.h create mode 100644 src/st/st-shadow.c create mode 100644 src/st/st-shadow.h diff --git a/src/Makefile-st.am b/src/Makefile-st.am index bd7d15bbb..bf440162f 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -83,6 +83,8 @@ st_source_h = \ st/st-scrollable.h \ st/st-scroll-bar.h \ st/st-scroll-view.h \ + st/st-shadow-texture.h \ + st/st-shadow.h \ st/st-subtexture.h \ st/st-table.h \ st/st-table-child.h \ @@ -122,6 +124,8 @@ st_source_c = \ st/st-scrollable.c \ st/st-scroll-bar.c \ st/st-scroll-view.c \ + st/st-shadow-texture.c \ + st/st-shadow.c \ st/st-subtexture.c \ st/st-table.c \ st/st-table-child.c \ diff --git a/src/st/st-shadow-texture.c b/src/st/st-shadow-texture.c new file mode 100644 index 000000000..57090ba1c --- /dev/null +++ b/src/st/st-shadow-texture.c @@ -0,0 +1,274 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include +#include + +#include "st-shadow-texture.h" + +/** + * SECTION: st-shadow-texture + * @short_description: a class for creating soft shadow textures + * + * #StShadowTexture is a #ClutterTexture holding a soft shadow texture for + * another #ClutterActor. + * It is used to implement the box-shadow property in StWidget and should + * not be used stand-alone. + */ + +struct _StShadowTexture { + ClutterTexture parent; + + CoglColor color; + gdouble sigma; + gdouble blur_radius; +}; + +struct _StShadowTextureClass { + ClutterTextureClass parent_class; +}; + +G_DEFINE_TYPE (StShadowTexture, st_shadow_texture, CLUTTER_TYPE_TEXTURE); + +static gdouble * +calculate_gaussian_kernel (gdouble sigma, guint n_values) +{ + gdouble *ret, sum; + gdouble exp_divisor; + gint half, i; + + g_return_val_if_fail ((int) sigma > 0, NULL); + + half = n_values / 2; + + ret = g_malloc (n_values * sizeof (gdouble)); + sum = 0.0; + + exp_divisor = 2 * sigma * sigma; + + /* n_values of 1D Gauss function */ + for (i = 0; i < n_values; i++) + { + ret[i] = exp (-(i - half) * (i - half) / exp_divisor); + sum += ret[i]; + } + + /* normalize */ + for (i = 0; i < n_values; i++) + ret[i] /= sum; + + return ret; +} + +static void +st_shadow_texture_create_shadow (StShadowTexture *st, + ClutterActor *actor) +{ + CoglHandle texture, material; + guchar *pixels_in, *pixels_out; + gint width_in, height_in, rowstride_in; + gint width_out, height_out, rowstride_out; + + g_return_if_fail (ST_IS_SHADOW_TEXTURE (st)); + + /* Right now we only deal with actors of type ClutterTexture. + It would be nice to extend this to generic actors with some + clutter_texture_new_from_actor magic in the future */ + g_return_if_fail (CLUTTER_IS_TEXTURE (actor)); + + texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (actor)); + if (texture == COGL_INVALID_HANDLE) + return; + + width_in = cogl_texture_get_width (texture); + height_in = cogl_texture_get_height (texture); + rowstride_in = (width_in + 3) & ~3; + + pixels_in = g_malloc0 (rowstride_in * height_in); + + cogl_texture_get_data (texture, COGL_PIXEL_FORMAT_A_8, + rowstride_in, pixels_in); + cogl_texture_unref (texture); + + if ((guint) st->blur_radius == 0) + { + width_out = width_in; + height_out = height_in; + rowstride_out = rowstride_in; + pixels_out = g_memdup (pixels_in, rowstride_out * height_out); + } + else + { + gdouble *kernel; + guchar *line; + gint n_values, half; + gint x_in, y_in, x_out, y_out, i; + + n_values = (gint) 5 * st->sigma; + half = n_values / 2; + + width_out = width_in + 2 * half; + height_out = height_in + 2 * half; + rowstride_out = (width_out + 3) & ~3; + + pixels_out = g_malloc0 (rowstride_out * height_out); + line = g_malloc0 (rowstride_out); + + kernel = calculate_gaussian_kernel (st->sigma, n_values); + + /* vertical blur */ + for (x_in = 0; x_in < width_in; x_in++) + for (y_out = 0; y_out < height_out; y_out++) + { + guchar *pixel_in, *pixel_out; + gint i0, i1; + + y_in = y_out - half; + + /* We read from the source at 'y = y_in + i - half'; clamp the + * full i range [0, n_values) so that y is in [0, height_in). + */ + i0 = MAX (half - y_in, 0); + i1 = MIN (height_in + half - y_in, n_values); + + pixel_in = pixels_in + (y_in + i0 - half) * rowstride_in + x_in; + pixel_out = pixels_out + y_out * rowstride_out + (x_in + half); + + for (i = i0; i < i1; i++) + { + *pixel_out += *pixel_in * kernel[i]; + pixel_in += rowstride_in; + } + } + + /* horizontal blur */ + for (y_out = 0; y_out < height_out; y_out++) + { + memcpy (line, pixels_out + y_out * rowstride_out, rowstride_out); + + for (x_out = 0; x_out < width_out; x_out++) + { + gint i0, i1; + guchar *pixel_out, *pixel_in; + + /* We read from the source at 'x = x_out + i - half'; clamp the + * full i range [0, n_values) so that x is in [0, width_out). + */ + i0 = MAX (half - x_out, 0); + i1 = MIN (width_out + half - x_out, n_values); + + pixel_in = line + x_out + i0 - half; + pixel_out = pixels_out + rowstride_out * y_out + x_out; + + *pixel_out = 0; + for (i = i0; i < i1; i++) + { + *pixel_out += *pixel_in * kernel[i]; + pixel_in++; + } + } + } + g_free (kernel); + g_free (line); + } + + material = cogl_material_new (); + texture = cogl_texture_new_from_data (width_out, + height_out, + COGL_TEXTURE_NONE, + COGL_PIXEL_FORMAT_A_8, + COGL_PIXEL_FORMAT_A_8, + rowstride_out, + pixels_out); + + cogl_material_set_layer_combine_constant (material, 0, &st->color); + cogl_material_set_layer (material, 0, texture); + + /* We ignore the material color, which encodes the overall opacity of the + * actor, so setting an ancestor of the shadow to partially opaque won't + * work. The easiest way to fix this would be to override paint(). */ + + cogl_material_set_layer_combine (material, 0, + "RGBA = MODULATE (CONSTANT, TEXTURE[A])", + NULL); + + clutter_texture_set_cogl_material (CLUTTER_TEXTURE (st), material); + + cogl_texture_unref (texture); + cogl_material_unref (material); + + g_free (pixels_in); + g_free (pixels_out); +} + + +/** + * st_shadow_texture_adjust_allocation: + * @shadow: a #StShadowTexture + * @allocation: the original allocation of @shadow + * + * Adjust @allocation to account for size change caused by blurrimg + */ +void +st_shadow_texture_adjust_allocation (StShadowTexture *shadow, + ClutterActorBox *allocation) +{ + g_return_if_fail (ST_IS_SHADOW_TEXTURE (shadow)); + g_return_if_fail (allocation != NULL); + + allocation->x1 -= shadow->blur_radius; + allocation->y1 -= shadow->blur_radius; + allocation->x2 += shadow->blur_radius; + allocation->y2 += shadow->blur_radius; +} + + +/** + * st_shadow_texture_new: + * @actor: the original actor + * @color: (allow-none): the shadow color + * @blur: the shadow's blur radius + * + * Create a shadow texture for @actor. When %NULL is passed for @color, it + * defaults to fully opaque black. + * + * Returns: a new #ClutterActor holding a shadow texture for @actor + */ +ClutterActor * +st_shadow_texture_new (ClutterActor *actor, + ClutterColor *color, + gdouble blur) +{ + StShadowTexture *st = g_object_new (ST_TYPE_SHADOW_TEXTURE, NULL); + + if (color) + { + cogl_color_set_from_4ub (&st->color, + color->red, color->green, + color->blue, color->alpha); + cogl_color_premultiply (&st->color); + } + + st->blur_radius = blur; + /* we use an approximation of the sigma - blur radius relationship used + in Firefox for doing SVG blurs; see + http://mxr.mozilla.org/mozilla-central/source/gfx/thebes/src/gfxBlur.cpp#280 + */ + st->sigma = blur / 1.9; + + st_shadow_texture_create_shadow (st, actor); + + return CLUTTER_ACTOR (st); +} + +static void +st_shadow_texture_init (StShadowTexture *st) +{ + st->sigma = 0.0; + st->blur_radius = 0.0; + + cogl_color_set_from_4ub (&st->color, 0x0, 0x0, 0x0, 0xff); +} + +static void +st_shadow_texture_class_init (StShadowTextureClass *klass) +{ +} diff --git a/src/st/st-shadow-texture.h b/src/st/st-shadow-texture.h new file mode 100644 index 000000000..3f5b28a25 --- /dev/null +++ b/src/st/st-shadow-texture.h @@ -0,0 +1,30 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __ST_SHADOW_TEXTURE__ +#define __ST_SHADOW_TEXTURE__ + +#include + +G_BEGIN_DECLS + +typedef struct _StShadowTexture StShadowTexture; +typedef struct _StShadowTextureClass StShadowTextureClass; + +#define ST_TYPE_SHADOW_TEXTURE (st_shadow_texture_get_type ()) +#define ST_SHADOW_TEXTURE(object) (G_TYPE_CHECK_INSTANCE_CAST((object),ST_TYPE_SHADOW_TEXTURE, StShadowTexture)) +#define ST_IS_SHADOW_TEXTURE(object) (G_TYPE_CHECK_INSTANCE_TYPE((object),ST_TYPE_SHADOW_TEXTURE)) +#define ST_SHADOW_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ST_TYPE_SHADOW_TEXTURE, StShadowTextureClass)) +#define ST_IS_SHADOW_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ST_TYPE_SHADOW_TEXTURE)) +#define ST_SHADOW_TEXTURE_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), ST_TYPE_SHADOW_TEXTURE, StShadowTextureClass)) + +GType st_shadow_texture_get_type (void) G_GNUC_CONST; + +ClutterActor *st_shadow_texture_new (ClutterActor *actor, + ClutterColor *color, + gdouble blur_radius); + +void st_shadow_texture_adjust_allocation (StShadowTexture *shadow, + ClutterActorBox *allocation); + +G_END_DECLS + +#endif /* __ST_SHADOW_TEXTURE__ */ diff --git a/src/st/st-shadow.c b/src/st/st-shadow.c new file mode 100644 index 000000000..0f11a6c68 --- /dev/null +++ b/src/st/st-shadow.c @@ -0,0 +1,89 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + +#include "st-shadow.h" + +/** + * SECTION: st-shadow + * @short_description: Boxed type for -st-shadow attributes + * + * #StShadow is a boxed type for storing attributes of the -st-shadow + * property, modelled liberally after the CSS3 box-shadow property. + * See http://www.css3.info/preview/box-shadow/ + * + */ + +/** + * st_shadow_new: + * @color: shadow's color + * @xoffset: horizontal offset + * @yoffset: vertical offset + * @blur: blur radius + * + * Creates a new #StShadow + * + * Returns: the newly allocated shadow. Use st_shadow_free() when done + */ +StShadow * +st_shadow_new (ClutterColor *color, + gdouble xoffset, + gdouble yoffset, + gdouble blur) +{ + StShadow *shadow; + + shadow = g_slice_new (StShadow); + + shadow->color = *color; + shadow->xoffset = xoffset; + shadow->yoffset = yoffset; + shadow->blur = blur; + + return shadow; +} + +/** + * st_shadow_copy: + * @shadow: a #StShadow + * + * Makes a copy of @shadow. + * + * Returns: an allocated copy of @shadow - the result must be freed with + * st_shadow_free() when done + */ +StShadow * +st_shadow_copy (const StShadow *shadow) +{ + g_return_val_if_fail (shadow != NULL, NULL); + + return g_slice_dup (StShadow, shadow); +} + +/** + * st_shadow_free: + * @shadow: a #StShadow + * + * Frees the shadow structure created with st_shadow_new() or + * st_shadow_copy() + */ +void +st_shadow_free (StShadow *shadow) +{ + g_return_if_fail (shadow != NULL); + + g_slice_free (StShadow, shadow); +} + +GType +st_shadow_get_type (void) +{ + static GType _st_shadow_type = 0; + + if (G_UNLIKELY (_st_shadow_type == 0)) + _st_shadow_type = + g_boxed_type_register_static ("StShadow", + (GBoxedCopyFunc) st_shadow_copy, + (GBoxedFreeFunc) st_shadow_free); + + return _st_shadow_type; +} diff --git a/src/st/st-shadow.h b/src/st/st-shadow.h new file mode 100644 index 000000000..cf5bffb51 --- /dev/null +++ b/src/st/st-shadow.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __ST_SHADOW__ +#define __ST_SHADOW__ + +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SHADOW (st_shadow_get_type ()) + +typedef struct _StShadow StShadow; + +/** + * StShadow: + * @color: shadow's color + * @xoffset: horizontal offset - positive values mean placement to the right, + * negative values placement to the left of the element. + * @yoffset: vertical offset - positive values mean placement below, negative + * values placement above the element. + * @blur: shadow's blur radius - a value of 0.0 will result in a hard shadow. + * + * Attributes of the -st-shadow property. + */ +struct _StShadow { + ClutterColor color; + gdouble xoffset; + gdouble yoffset; + gdouble blur; +}; + +GType st_shadow_get_type (void) G_GNUC_CONST; + +StShadow *st_shadow_new (ClutterColor *color, + gdouble xoffset, + gdouble yoffset, + gdouble blur); +StShadow *st_shadow_copy (const StShadow *shadow); +void st_shadow_free (StShadow *shadow); + +G_END_DECLS + +#endif /* __ST_SHADOW__ */ diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c index fb14c68f5..f8998e843 100644 --- a/src/st/st-theme-node.c +++ b/src/st/st-theme-node.c @@ -39,6 +39,7 @@ struct _StThemeNode { char *background_image; StBorderImage *border_image; + StShadow *shadow; GType element_type; char *element_id; @@ -57,6 +58,7 @@ struct _StThemeNode { guint background_computed : 1; guint foreground_computed : 1; guint border_image_computed : 1; + guint shadow_computed : 1; guint link_type : 2; }; @@ -118,6 +120,12 @@ st_theme_node_finalize (GObject *object) node->border_image = NULL; } + if (node->shadow) + { + st_shadow_free (node->shadow); + node->shadow = NULL; + } + if (node->background_image) g_free (node->background_image); @@ -2116,6 +2124,87 @@ st_theme_node_get_border_image (StThemeNode *node) return NULL; } +/** + * st_theme_node_get_shadow: + * @node: a #StThemeNode + * + * Gets the value for the -st-shadow style property + * + * Return value: (transfer none): the node's shadow, or %NULL + * if node has no shadow + */ +StShadow * +st_theme_node_get_shadow (StThemeNode *node) +{ + int i; + + if (node->shadow_computed) + return node->shadow; + + node->shadow = NULL; + node->shadow_computed = TRUE; + + ensure_properties (node); + + for (i = node->n_properties - 1; i >= 0; i--) + { + CRDeclaration *decl = node->properties[i]; + + if (strcmp (decl->property->stryng->str, "-st-shadow") == 0) + { + /* Set value for width/color/blur in any order */ + CRTerm *term; + ClutterColor color = { 0x0, 0x0, 0x0, 0xff }; + gdouble xoffset = 0.; + gdouble yoffset = 0.; + gdouble blur = 0.; + int n_offsets = 0; + + for (term = decl->value; term; term = term->next) + { + GetFromTermResult result; + + if (term->type == TERM_NUMBER) + { + gdouble value; + gdouble multiplier; + + multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.; + result = get_length_from_term (node, term, FALSE, &value); + if (result != VALUE_NOT_FOUND) + { + switch (n_offsets++) + { + case 0: + xoffset = multiplier * value; + break; + case 1: + yoffset = multiplier * value; + break; + case 2: + if (multiplier < 0) + g_warning ("Negative blur values are " + "not allowed"); + blur = value; + } + continue; + } + } + + result = get_color_from_term (node, term, &color); + if (result != VALUE_NOT_FOUND) + { + continue; + } + } + node->shadow = st_shadow_new (&color, xoffset, yoffset, blur); + + return node->shadow; + } + } + return NULL; +} + static float get_width_inc (StThemeNode *node) { diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h index 0f9221463..31fb08ed5 100644 --- a/src/st/st-theme-node.h +++ b/src/st/st-theme-node.h @@ -4,6 +4,7 @@ #include #include "st-border-image.h" +#include "st-shadow.h" G_BEGIN_DECLS @@ -142,6 +143,7 @@ StTextDecoration st_theme_node_get_text_decoration (StThemeNode *node); const PangoFontDescription *st_theme_node_get_font (StThemeNode *node); StBorderImage *st_theme_node_get_border_image (StThemeNode *node); +StShadow *st_theme_node_get_shadow (StThemeNode *node); /* Helpers for get_preferred_width()/get_preferred_height() ClutterActor vfuncs */ void st_theme_node_adjust_for_height (StThemeNode *node, diff --git a/src/st/st-widget.c b/src/st/st-widget.c index ba2c7a63e..023be7d2a 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -37,6 +37,7 @@ #include "st-marshal.h" #include "st-private.h" +#include "st-shadow-texture.h" #include "st-texture-cache.h" #include "st-texture-frame.h" #include "st-theme-context.h" @@ -57,11 +58,15 @@ struct _StWidgetPrivate ClutterActor *border_image; ClutterActor *background_image; + ClutterActor *background_image_shadow; ClutterColor bg_color; StGradientType bg_gradient_type; ClutterColor bg_gradient_end; + gdouble shadow_xoffset; + gdouble shadow_yoffset; + gboolean is_stylable : 1; gboolean has_tooltip : 1; gboolean is_style_dirty : 1; @@ -227,6 +232,12 @@ st_widget_dispose (GObject *gobject) priv->border_image = NULL; } + if (priv->background_image_shadow) + { + clutter_actor_unparent (priv->background_image_shadow); + priv->background_image_shadow = NULL; + } + if (priv->tooltip) { ClutterContainer *parent; @@ -356,6 +367,28 @@ st_widget_allocate (ClutterActor *actor, frame_box.y2 = frame_box.y1 + h; } + if (priv->background_image_shadow) + { + StShadowTexture *shadow; + ClutterActorBox shadow_box; + + shadow_box.x1 = frame_box.x1 + priv->shadow_xoffset; + shadow_box.y1 = frame_box.y1 + priv->shadow_yoffset; + shadow_box.x2 = frame_box.x2 + priv->shadow_xoffset; + shadow_box.y2 = frame_box.y2 + priv->shadow_yoffset; + + /* The shadow texture is larger than the original image due + to blurring, so we let it adjust its size. + When the original image has been scaled, this will change + the effective blur radius - we ignore this for now. */ + shadow = ST_SHADOW_TEXTURE (priv->background_image_shadow); + st_shadow_texture_adjust_allocation (shadow, &shadow_box); + + clutter_actor_allocate (priv->background_image_shadow, + &shadow_box, flags); + } + + clutter_actor_allocate (CLUTTER_ACTOR (priv->background_image), &frame_box, flags); @@ -497,7 +530,11 @@ st_widget_paint (ClutterActor *self) klass->draw_background (ST_WIDGET (self)); if (priv->background_image != NULL) - clutter_actor_paint (priv->background_image); + { + if (priv->background_image_shadow) + clutter_actor_paint (priv->background_image_shadow); + clutter_actor_paint (priv->background_image); + } } static void @@ -527,6 +564,9 @@ st_widget_map (ClutterActor *actor) st_widget_ensure_style ((StWidget*) actor); + if (priv->background_image_shadow) + clutter_actor_map (priv->background_image_shadow); + if (priv->border_image) clutter_actor_map (priv->border_image); @@ -544,6 +584,9 @@ st_widget_unmap (ClutterActor *actor) CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor); + if (priv->background_image_shadow) + clutter_actor_unmap (priv->background_image_shadow); + if (priv->border_image) clutter_actor_unmap (priv->border_image); @@ -659,6 +702,7 @@ st_widget_real_style_changed (StWidget *self) StWidgetPrivate *priv = ST_WIDGET (self)->priv; StThemeNode *theme_node; StBorderImage *border_image; + StShadow *shadow; StTextureCache *texture_cache; ClutterTexture *texture; const char *bg_file = NULL; @@ -706,6 +750,12 @@ st_widget_real_style_changed (StWidget *self) has_changed = TRUE; } + if (priv->background_image_shadow) + { + clutter_actor_unparent (priv->background_image_shadow); + priv->background_image_shadow = NULL; + } + if (priv->border_image) { clutter_actor_unparent (priv->border_image); @@ -877,7 +927,39 @@ st_widget_real_style_changed (StWidget *self) relayout_needed = TRUE; } - /* If there are any properties above that need to cause a relayout thay + /* CSS based drop shadows + * + * Drop shadows in ST are modelled after the CSS3 box-shadow property; + * see http://www.css3.info/preview/box-shadow/ for a detailed description. + * + * While the syntax of the property is mostly identical - we do not support + * multiple shadows and allow for a more liberal placement of the color + * parameter - its interpretation defers significantly in that the shadow's + * shape is not determined by the bounding box, but by the CSS background + * image (we could exend this in the future to take other CSS properties + * like boder and background color into account). + */ + shadow = st_theme_node_get_shadow (theme_node); + if (shadow != NULL) + { + priv->shadow_xoffset = shadow->xoffset; + priv->shadow_yoffset = shadow->yoffset; + + if (priv->background_image) + { + priv->background_image_shadow = + st_shadow_texture_new (priv->background_image, + &shadow->color, + shadow->blur); + + clutter_actor_set_parent (priv->background_image_shadow, + CLUTTER_ACTOR (self)); + has_changed = TRUE; + relayout_needed = TRUE; + } + } + + /* If there are any properties above that need to cause a relayout they * should set this flag. */ if (has_changed)