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
This commit is contained in:
Florian Müllner 2009-11-21 04:19:56 +01:00
parent a2cae50e0e
commit 2dfe113a42
8 changed files with 614 additions and 2 deletions

View File

@ -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 \

274
src/st/st-shadow-texture.c Normal file
View File

@ -0,0 +1,274 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include <math.h>
#include <string.h>
#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)
{
}

View File

@ -0,0 +1,30 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#ifndef __ST_SHADOW_TEXTURE__
#define __ST_SHADOW_TEXTURE__
#include <clutter/clutter.h>
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__ */

89
src/st/st-shadow.c Normal file
View File

@ -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;
}

42
src/st/st-shadow.h Normal file
View File

@ -0,0 +1,42 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#ifndef __ST_SHADOW__
#define __ST_SHADOW__
#include <clutter/clutter.h>
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__ */

View File

@ -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)
{

View File

@ -4,6 +4,7 @@
#include <clutter/clutter.h>
#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,

View File

@ -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)