/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-theme-node-drawing.c: Code to draw themed elements
*
* Copyright 2009, 2010 Red Hat, Inc.
* Copyright 2009, 2010 Florian Müllner
* Copyright 2010 Intel Corporation.
*
* Contains code derived from:
* rectangle.c: Rounded rectangle.
* Copyright 2008 litl, LLC.
* st-texture-frame.h: Expandible texture actor
* Copyright 2007 OpenedHand
* Copyright 2009 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
#include
#include
#include "st-shadow.h"
#include "st-private.h"
#include "st-theme-private.h"
#include "st-theme-context.h"
#include "st-texture-cache.h"
#include "st-theme-node-private.h"
/****
* Rounded corners
****/
typedef struct {
ClutterColor color;
ClutterColor border_color_1;
ClutterColor border_color_2;
guint radius;
guint border_width_1;
guint border_width_2;
} StCornerSpec;
static void
elliptical_arc (cairo_t *cr,
double x_center,
double y_center,
double x_radius,
double y_radius,
double angle1,
double angle2)
{
cairo_save (cr);
cairo_translate (cr, x_center, y_center);
cairo_scale (cr, x_radius, y_radius);
cairo_arc (cr, 0, 0, 1.0, angle1, angle2);
cairo_restore (cr);
}
static CoglHandle
create_corner_material (StCornerSpec *corner)
{
CoglHandle texture;
cairo_t *cr;
cairo_surface_t *surface;
guint rowstride;
guint8 *data;
guint size;
guint max_border_width;
max_border_width = MAX(corner->border_width_2, corner->border_width_1);
size = 2 * MAX(max_border_width, corner->radius);
rowstride = size * 4;
data = g_new0 (guint8, size * rowstride);
surface = cairo_image_surface_create_for_data (data,
CAIRO_FORMAT_ARGB32,
size, size,
rowstride);
cr = cairo_create (surface);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_scale (cr, size, size);
if (max_border_width <= corner->radius)
{
double x_radius, y_radius;
if (max_border_width != 0)
{
cairo_set_source_rgba (cr,
corner->border_color_1.red / 255.,
corner->border_color_1.green / 255.,
corner->border_color_1.blue / 255.,
corner->border_color_1.alpha / 255.);
cairo_arc (cr, 0.5, 0.5, 0.5, 0, 2 * M_PI);
cairo_fill (cr);
}
cairo_set_source_rgba (cr,
corner->color.red / 255.,
corner->color.green / 255.,
corner->color.blue / 255.,
corner->color.alpha / 255.);
x_radius = 0.5 * (1.0 - (double) corner->border_width_2 / corner->radius);
y_radius = 0.5 * (1.0 - (double) corner->border_width_1 / corner->radius);
/* TOPRIGHT */
elliptical_arc (cr,
0.5, 0.5,
x_radius, y_radius,
3 * M_PI / 2, 2 * M_PI);
/* BOTTOMRIGHT */
elliptical_arc (cr,
0.5, 0.5,
x_radius, y_radius,
0, M_PI / 2);
/* TOPLEFT */
elliptical_arc (cr,
0.5, 0.5,
x_radius, y_radius,
M_PI, 3 * M_PI / 2);
/* BOTTOMLEFT */
elliptical_arc (cr,
0.5, 0.5,
x_radius, y_radius,
M_PI / 2, M_PI);
cairo_fill (cr);
}
else
{
double radius;
radius = (gdouble)corner->radius / max_border_width;
cairo_set_source_rgba (cr,
corner->border_color_1.red / 255.,
corner->border_color_1.green / 255.,
corner->border_color_1.blue / 255.,
corner->border_color_1.alpha / 255.);
cairo_arc (cr, radius, radius, radius, M_PI, 3 * M_PI / 2);
cairo_line_to (cr, 1.0 - radius, 0.0);
cairo_arc (cr, 1.0 - radius, radius, radius, 3 * M_PI / 2, 2 * M_PI);
cairo_line_to (cr, 1.0, 1.0 - radius);
cairo_arc (cr, 1.0 - radius, 1.0 - radius, radius, 0, M_PI / 2);
cairo_line_to (cr, radius, 1.0);
cairo_arc (cr, radius, 1.0 - radius, radius, M_PI / 2, M_PI);
cairo_fill (cr);
}
cairo_destroy (cr);
cairo_surface_destroy (surface);
texture = cogl_texture_new_from_data (size, size,
COGL_TEXTURE_NONE,
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
COGL_PIXEL_FORMAT_BGRA_8888_PRE,
#else
COGL_PIXEL_FORMAT_ARGB_8888_PRE,
#endif
COGL_PIXEL_FORMAT_ANY,
rowstride,
data);
g_free (data);
g_assert (texture != COGL_INVALID_HANDLE);
return texture;
}
static char *
corner_to_string (StCornerSpec *corner)
{
return g_strdup_printf ("st-theme-node-corner:%02x%02x%02x%02x,%02x%02x%02x%02x,%02x%02x%02x%02x,%u,%u,%u",
corner->color.red, corner->color.blue, corner->color.green, corner->color.alpha,
corner->border_color_1.red, corner->border_color_1.green, corner->border_color_1.blue, corner->border_color_1.alpha,
corner->border_color_2.red, corner->border_color_2.green, corner->border_color_2.blue, corner->border_color_2.alpha,
corner->radius,
corner->border_width_1,
corner->border_width_2);
}
typedef struct {
StThemeNode *node;
StCornerSpec *corner;
} LoadCornerData;
static CoglHandle
load_corner (StTextureCache *cache,
const char *key,
void *datap,
GError **error)
{
LoadCornerData *data = datap;
return create_corner_material (data->corner);
}
/* To match the CSS specification, we want the border to look like it was
* drawn over the background. But actually drawing the border over the
* background will produce slightly bad antialiasing at the edges, so
* compute the effective border color instead.
*/
#define NORM(x) (t = (x) + 127, (t + (t >> 8)) >> 8)
#define MULT(c,a) NORM(c*a)
static void
premultiply (ClutterColor *color)
{
guint t;
color->red = MULT (color->red, color->alpha);
color->green = MULT (color->green, color->alpha);
color->blue = MULT (color->blue, color->alpha);
}
static void
unpremultiply (ClutterColor *color)
{
if (color->alpha != 0)
{
color->red = (color->red * 255 + 127) / color->alpha;
color->green = (color->green * 255 + 127) / color->alpha;
color->blue = (color->blue * 255 + 127) / color->alpha;
}
}
static void
over (const ClutterColor *source,
const ClutterColor *destination,
ClutterColor *result)
{
guint t;
ClutterColor src = *source;
ClutterColor dst = *destination;
premultiply (&src);
premultiply (&dst);
result->alpha = src.alpha + NORM ((255 - src.alpha) * dst.alpha);
result->red = src.red + NORM ((255 - src.alpha) * dst.red);
result->green = src.green + NORM ((255 - src.alpha) * dst.green);
result->blue = src.blue + NORM ((255 - src.alpha) * dst.blue);
unpremultiply (result);
}
static void
st_theme_node_get_corner_border_widths (StThemeNode *node,
StCorner corner_id,
guint *border_width_1,
guint *border_width_2)
{
switch (corner_id)
{
case ST_CORNER_TOPLEFT:
if (border_width_1)
*border_width_1 = node->border_width[ST_SIDE_TOP];
if (border_width_2)
*border_width_2 = node->border_width[ST_SIDE_LEFT];
break;
case ST_CORNER_TOPRIGHT:
if (border_width_1)
*border_width_1 = node->border_width[ST_SIDE_TOP];
if (border_width_2)
*border_width_2 = node->border_width[ST_SIDE_RIGHT];
break;
case ST_CORNER_BOTTOMRIGHT:
if (border_width_1)
*border_width_1 = node->border_width[ST_SIDE_BOTTOM];
if (border_width_2)
*border_width_2 = node->border_width[ST_SIDE_RIGHT];
break;
case ST_CORNER_BOTTOMLEFT:
if (border_width_1)
*border_width_1 = node->border_width[ST_SIDE_BOTTOM];
if (border_width_2)
*border_width_2 = node->border_width[ST_SIDE_LEFT];
break;
}
}
static CoglHandle
st_theme_node_lookup_corner (StThemeNode *node,
StCorner corner_id)
{
CoglHandle texture, material;
char *key;
StTextureCache *cache;
StCornerSpec corner;
LoadCornerData data;
if (node->border_radius[corner_id] == 0)
return COGL_INVALID_HANDLE;
cache = st_texture_cache_get_default ();
corner.radius = node->border_radius[corner_id];
corner.color = node->background_color;
st_theme_node_get_corner_border_widths (node, corner_id,
&corner.border_width_1,
&corner.border_width_2);
switch (corner_id)
{
case ST_CORNER_TOPLEFT:
over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
break;
case ST_CORNER_TOPRIGHT:
over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
break;
case ST_CORNER_BOTTOMRIGHT:
over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
break;
case ST_CORNER_BOTTOMLEFT:
over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
break;
}
if (corner.color.alpha == 0 &&
corner.border_color_1.alpha == 0 &&
corner.border_color_2.alpha == 0)
return COGL_INVALID_HANDLE;
key = corner_to_string (&corner);
data.node = node;
data.corner = &corner;
texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_NONE, load_corner, &data, NULL);
material = _st_create_texture_material (texture);
cogl_handle_unref (texture);
g_free (key);
return material;
}
static void
get_background_position (StThemeNode *self,
const ClutterActorBox *allocation,
ClutterActorBox *result)
{
gfloat w, h;
result->x1 = result->y1 = 0;
result->x2 = allocation->x2 - allocation->x1;
result->y2 = allocation->y2 - allocation->y1;
w = cogl_texture_get_width (self->background_texture);
h = cogl_texture_get_height (self->background_texture);
/* scale the background into the allocated bounds, when not being absolutely positioned */
if ((w > result->x2 || h > result->y2) && !self->background_position_set)
{
gint new_h, new_w, offset;
gint box_w, box_h;
box_w = (int) result->x2;
box_h = (int) result->y2;
/* scale to fit */
new_h = (int)((h / w) * ((gfloat) box_w));
new_w = (int)((w / h) * ((gfloat) box_h));
if (new_h > box_h)
{
/* center for new width */
offset = ((box_w) - new_w) * 0.5;
result->x1 = offset;
result->x2 = offset + new_w;
result->y2 = box_h;
}
else
{
/* center for new height */
offset = ((box_h) - new_h) * 0.5;
result->y1 = offset;
result->y2 = offset + new_h;
result->x2 = box_w;
}
}
else
{
/* honor the specified position if any */
if (self->background_position_set)
{
result->x1 = self->background_position_x;
result->y1 = self->background_position_y;
}
else
{
/* center the background on the widget */
result->x1 = (int)(((allocation->x2 - allocation->x1) / 2) - (w / 2));
result->y1 = (int)(((allocation->y2 - allocation->y1) / 2) - (h / 2));
}
result->x2 = result->x1 + w;
result->y2 = result->y1 + h;
}
}
/* Use of this function marks code which doesn't support
* non-uniform colors.
*/
static void
get_arbitrary_border_color (StThemeNode *node,
ClutterColor *color)
{
if (color)
st_theme_node_get_border_color (node, ST_SIDE_TOP, color);
}
static CoglHandle
st_theme_node_render_gradient (StThemeNode *node)
{
StBorderImage *border_image;
CoglHandle texture;
int radius[4], i;
cairo_t *cr;
cairo_surface_t *surface;
cairo_pattern_t *pattern;
ClutterColor border_color;
int border_width[4];
guint rowstride;
guchar *data;
border_image = st_theme_node_get_border_image (node);
rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, node->alloc_width);
data = g_new0 (guchar, node->alloc_height * rowstride);
surface = cairo_image_surface_create_for_data (data,
CAIRO_FORMAT_ARGB32,
node->alloc_width,
node->alloc_height,
rowstride);
cr = cairo_create (surface);
/* TODO - support non-uniform border colors */
get_arbitrary_border_color (node, &border_color);
for (i = 0; i < 4; i++)
{
border_width[i] = st_theme_node_get_border_width (node, i);
radius[i] = st_theme_node_get_border_radius (node, i);
}
if (node->background_gradient_type == ST_GRADIENT_VERTICAL)
pattern = cairo_pattern_create_linear (0, 0, 0, node->alloc_height);
else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL)
pattern = cairo_pattern_create_linear (0, 0, node->alloc_width, 0);
else
{
gdouble cx, cy;
cx = node->alloc_width / 2.;
cy = node->alloc_height / 2.;
pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy));
}
cairo_pattern_add_color_stop_rgba (pattern, 0,
node->background_color.red / 255.,
node->background_color.green / 255.,
node->background_color.blue / 255.,
node->background_color.alpha / 255.);
cairo_pattern_add_color_stop_rgba (pattern, 1,
node->background_gradient_end.red / 255.,
node->background_gradient_end.green / 255.,
node->background_gradient_end.blue / 255.,
node->background_gradient_end.alpha / 255.);
/* Create a path for the background's outline first */
if (radius[ST_CORNER_TOPLEFT] > 0)
cairo_arc (cr,
radius[ST_CORNER_TOPLEFT],
radius[ST_CORNER_TOPLEFT],
radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2);
else
cairo_move_to (cr, 0, 0);
cairo_line_to (cr, node->alloc_width - radius[ST_CORNER_TOPRIGHT], 0);
if (radius[ST_CORNER_TOPRIGHT] > 0)
cairo_arc (cr,
node->alloc_width - radius[ST_CORNER_TOPRIGHT],
radius[ST_CORNER_TOPRIGHT],
radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI);
cairo_line_to (cr, node->alloc_width, node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT]);
if (radius[ST_CORNER_BOTTOMRIGHT] > 0)
cairo_arc (cr,
node->alloc_width - radius[ST_CORNER_BOTTOMRIGHT],
node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT],
radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2);
cairo_line_to (cr, radius[ST_CORNER_BOTTOMLEFT], node->alloc_height);
if (radius[ST_CORNER_BOTTOMLEFT] > 0)
cairo_arc (cr,
radius[ST_CORNER_BOTTOMLEFT],
node->alloc_height - radius[ST_CORNER_BOTTOMLEFT],
radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI);
cairo_close_path (cr);
/* If we have a solid border, we fill the outline with the border
* color and create the inline shape for the background gradient;
* otherwise the outline shape is filled with the background
* gradient directly
*/
if (border_image == NULL &&
(border_width[ST_SIDE_TOP] > 0 ||
border_width[ST_SIDE_RIGHT] > 0 ||
border_width[ST_SIDE_BOTTOM] > 0 ||
border_width[ST_SIDE_LEFT] > 0))
{
cairo_set_source_rgba (cr,
border_color.red / 255.,
border_color.green / 255.,
border_color.blue / 255.,
border_color.alpha / 255.);
cairo_fill (cr);
if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP],
border_width[ST_SIDE_LEFT]))
elliptical_arc (cr,
radius[ST_CORNER_TOPLEFT],
radius[ST_CORNER_TOPLEFT],
radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT],
radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP],
M_PI, 3 * M_PI / 2);
else
cairo_move_to (cr,
border_width[ST_SIDE_LEFT],
border_width[ST_SIDE_TOP]);
cairo_line_to (cr,
node->alloc_width - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]),
border_width[ST_SIDE_TOP]);
if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP],
border_width[ST_SIDE_RIGHT]))
elliptical_arc (cr,
node->alloc_width - radius[ST_CORNER_TOPRIGHT],
radius[ST_CORNER_TOPRIGHT],
radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT],
radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP],
3 * M_PI / 2, 2 * M_PI);
else
cairo_line_to (cr,
node->alloc_width - border_width[ST_SIDE_RIGHT],
border_width[ST_SIDE_TOP]);
cairo_line_to (cr,
node->alloc_width - border_width[ST_SIDE_RIGHT],
node->alloc_height - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM]));
if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM],
border_width[ST_SIDE_RIGHT]))
elliptical_arc (cr,
node->alloc_width - radius[ST_CORNER_BOTTOMRIGHT],
node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT],
radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT],
radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM],
0, M_PI / 2);
else
cairo_line_to (cr,
node->alloc_width - border_width[ST_SIDE_RIGHT],
node->alloc_height - border_width[ST_SIDE_BOTTOM]);
cairo_line_to (cr,
MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]),
node->alloc_height - border_width[ST_SIDE_BOTTOM]);
if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM],
border_width[ST_SIDE_LEFT]))
elliptical_arc (cr,
radius[ST_CORNER_BOTTOMLEFT],
node->alloc_height - radius[ST_CORNER_BOTTOMLEFT],
radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT],
radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM],
M_PI / 2, M_PI);
else
cairo_line_to (cr,
border_width[ST_SIDE_LEFT],
node->alloc_height - border_width[ST_SIDE_BOTTOM]);
cairo_close_path (cr);
}
cairo_set_source (cr, pattern);
cairo_fill (cr);
cairo_pattern_destroy (pattern);
texture = cogl_texture_new_from_data (node->alloc_width, node->alloc_height,
COGL_TEXTURE_NONE,
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
COGL_PIXEL_FORMAT_BGRA_8888_PRE,
#elif G_BYTE_ORDER == G_BIG_ENDIAN
COGL_PIXEL_FORMAT_ARGB_8888_PRE,
#else
COGL_PIXEL_FORMAT_ANY,
#error unknown endianness type
#endif
COGL_PIXEL_FORMAT_ANY,
rowstride,
data);
cairo_destroy (cr);
cairo_surface_destroy (surface);
g_free (data);
return texture;
}
void
_st_theme_node_free_drawing_state (StThemeNode *node)
{
int corner_id;
if (node->background_texture != COGL_INVALID_HANDLE)
cogl_handle_unref (node->background_texture);
if (node->background_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->background_material);
if (node->background_shadow_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->background_shadow_material);
if (node->border_slices_texture != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_slices_texture);
if (node->border_slices_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_slices_material);
if (node->prerendered_texture != COGL_INVALID_HANDLE)
cogl_handle_unref (node->prerendered_texture);
if (node->prerendered_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->prerendered_material);
if (node->box_shadow_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->box_shadow_material);
for (corner_id = 0; corner_id < 4; corner_id++)
if (node->corner_material[corner_id] != COGL_INVALID_HANDLE)
cogl_handle_unref (node->corner_material[corner_id]);
_st_theme_node_init_drawing_state (node);
}
void
_st_theme_node_init_drawing_state (StThemeNode *node)
{
int corner_id;
node->background_texture = COGL_INVALID_HANDLE;
node->background_material = COGL_INVALID_HANDLE;
node->background_shadow_material = COGL_INVALID_HANDLE;
node->box_shadow_material = COGL_INVALID_HANDLE;
node->border_slices_texture = COGL_INVALID_HANDLE;
node->border_slices_material = COGL_INVALID_HANDLE;
node->prerendered_texture = COGL_INVALID_HANDLE;
node->prerendered_material = COGL_INVALID_HANDLE;
for (corner_id = 0; corner_id < 4; corner_id++)
node->corner_material[corner_id] = COGL_INVALID_HANDLE;
}
static void st_theme_node_paint_borders (StThemeNode *node,
const ClutterActorBox *box,
guint8 paint_opacity);
static void
st_theme_node_render_resources (StThemeNode *node,
float width,
float height)
{
StTextureCache *texture_cache;
StBorderImage *border_image;
StShadow *box_shadow_spec;
StShadow *background_image_shadow_spec;
const char *background_image;
texture_cache = st_texture_cache_get_default ();
/* FIXME - need to separate this into things that need to be recomputed on
* geometry change versus things that can be cached regardless, such as
* a background image.
*/
_st_theme_node_free_drawing_state (node);
node->alloc_width = width;
node->alloc_height = height;
_st_theme_node_ensure_background (node);
_st_theme_node_ensure_geometry (node);
box_shadow_spec = st_theme_node_get_box_shadow (node);
/* Load referenced images from disk and draw anything we need with cairo now */
border_image = st_theme_node_get_border_image (node);
if (border_image)
{
const char *filename;
filename = st_border_image_get_filename (border_image);
node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename);
}
if (node->background_gradient_type != ST_GRADIENT_NONE)
node->prerendered_texture = st_theme_node_render_gradient (node);
if (node->border_slices_texture)
node->border_slices_material = _st_create_texture_material (node->border_slices_texture);
else
node->border_slices_material = COGL_INVALID_HANDLE;
if (node->prerendered_texture)
node->prerendered_material = _st_create_texture_material (node->prerendered_texture);
else
node->prerendered_material = COGL_INVALID_HANDLE;
if (box_shadow_spec)
{
if (node->border_slices_texture != COGL_INVALID_HANDLE)
node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
node->border_slices_texture);
else if (node->prerendered_texture != COGL_INVALID_HANDLE)
node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
node->prerendered_texture);
else if (node->background_color.alpha > 0 ||
node->border_width[ST_SIDE_TOP] > 0 ||
node->border_width[ST_SIDE_LEFT] > 0 ||
node->border_width[ST_SIDE_RIGHT] > 0 ||
node->border_width[ST_SIDE_BOTTOM] > 0)
{
CoglHandle buffer, offscreen;
buffer = cogl_texture_new_with_size (width,
height,
COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_ANY);
offscreen = cogl_offscreen_new_to_texture (buffer);
if (offscreen != COGL_INVALID_HANDLE)
{
ClutterActorBox box = { 0, 0, width, height };
cogl_push_framebuffer (offscreen);
cogl_ortho (0, width, height, 0, 0, 1.0);
st_theme_node_paint_borders (node, &box, 0xFF);
cogl_pop_framebuffer ();
cogl_handle_unref (offscreen);
node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
buffer);
}
cogl_handle_unref (buffer);
}
}
background_image = st_theme_node_get_background_image (node);
background_image_shadow_spec = st_theme_node_get_background_image_shadow (node);
if (background_image != NULL)
{
CoglHandle texture;
texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image);
/* If no background position is specified, then we will automatically scale
* the background to fit within the node allocation. But, if a background
* position is specified, we won't scale the background, and it could
* potentially leak out of bounds. To prevent that, we subtexture from the
* in bounds area when necessary.
*/
if (node->background_position_set &&
(cogl_texture_get_width (texture) > width ||
cogl_texture_get_height (texture) > height))
{
CoglHandle subtexture;
subtexture = cogl_texture_new_from_sub_texture (texture,
0, 0,
width - node->background_position_x,
height - node->background_position_y);
cogl_handle_unref (texture);
node->background_texture = subtexture;
}
else
{
node->background_texture = texture;
}
node->background_material = _st_create_texture_material (node->background_texture);
if (background_image_shadow_spec)
{
node->background_shadow_material = _st_create_shadow_material (background_image_shadow_spec,
node->background_texture);
}
}
node->corner_material[ST_CORNER_TOPLEFT] =
st_theme_node_lookup_corner (node, ST_CORNER_TOPLEFT);
node->corner_material[ST_CORNER_TOPRIGHT] =
st_theme_node_lookup_corner (node, ST_CORNER_TOPRIGHT);
node->corner_material[ST_CORNER_BOTTOMRIGHT] =
st_theme_node_lookup_corner (node, ST_CORNER_BOTTOMRIGHT);
node->corner_material[ST_CORNER_BOTTOMLEFT] =
st_theme_node_lookup_corner (node, ST_CORNER_BOTTOMLEFT);
}
static void
paint_material_with_opacity (CoglHandle material,
ClutterActorBox *box,
guint8 paint_opacity)
{
cogl_material_set_color4ub (material,
paint_opacity, paint_opacity, paint_opacity, paint_opacity);
cogl_set_source (material);
cogl_rectangle (box->x1, box->y1, box->x2, box->y2);
}
static void
st_theme_node_paint_borders (StThemeNode *node,
const ClutterActorBox *box,
guint8 paint_opacity)
{
float width, height;
int border_width[4];
int max_border_radius = 0;
int max_width_radius[4];
int corner_id, side_id;
ClutterColor border_color;
guint8 alpha;
width = box->x2 - box->x1;
height = box->y2 - box->y1;
/* TODO - support non-uniform border colors */
get_arbitrary_border_color (node, &border_color);
for (side_id = 0; side_id < 4; side_id++)
border_width[side_id] = st_theme_node_get_border_width(node, side_id);
for (corner_id = 0; corner_id < 4; corner_id++)
{
guint border_width_1, border_width_2;
st_theme_node_get_corner_border_widths (node, corner_id,
&border_width_1, &border_width_2);
if (node->border_radius[corner_id] > max_border_radius)
max_border_radius = node->border_radius[corner_id];
max_width_radius[corner_id] = MAX(MAX(border_width_1, border_width_2),
node->border_radius[corner_id]);
}
/* borders */
if (border_width[ST_SIDE_TOP] > 0 ||
border_width[ST_SIDE_RIGHT] > 0 ||
border_width[ST_SIDE_BOTTOM] > 0 ||
border_width[ST_SIDE_LEFT] > 0)
{
ClutterColor effective_border;
gboolean skip_corner_1, skip_corner_2;
float x1, y1, x2, y2;
over (&border_color, &node->background_color, &effective_border);
alpha = paint_opacity * effective_border.alpha / 255;
if (alpha > 0)
{
cogl_set_source_color4ub (effective_border.red,
effective_border.green,
effective_border.blue,
alpha);
/* NORTH */
skip_corner_1 = node->border_radius[ST_CORNER_TOPLEFT] > 0;
skip_corner_2 = node->border_radius[ST_CORNER_TOPRIGHT] > 0;
x1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT] : 0;
y1 = 0;
x2 = skip_corner_2 ? width - max_width_radius[ST_CORNER_TOPRIGHT] : width;
y2 = border_width[ST_SIDE_TOP];
cogl_rectangle (x1, y1, x2, y2);
/* EAST */
skip_corner_1 = node->border_radius[ST_CORNER_TOPRIGHT] > 0;
skip_corner_2 = node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
x1 = width - border_width[ST_SIDE_RIGHT];
y1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT]
: border_width[ST_SIDE_TOP];
x2 = width;
y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
: height - border_width[ST_SIDE_BOTTOM];
cogl_rectangle (x1, y1, x2, y2);
/* SOUTH */
skip_corner_1 = node->border_radius[ST_CORNER_BOTTOMLEFT] > 0;
skip_corner_2 = node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
x1 = skip_corner_1 ? max_width_radius[ST_CORNER_BOTTOMLEFT] : 0;
y1 = height - border_width[ST_SIDE_BOTTOM];
x2 = skip_corner_2 ? width - max_width_radius[ST_CORNER_BOTTOMRIGHT]
: width;
y2 = height;
cogl_rectangle (x1, y1, x2, y2);
/* WEST */
skip_corner_1 = node->border_radius[ST_CORNER_TOPLEFT] > 0;
skip_corner_2 = node->border_radius[ST_CORNER_BOTTOMLEFT] > 0;
x1 = 0;
y1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT]
: border_width[ST_SIDE_TOP];
x2 = border_width[ST_SIDE_LEFT];
y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMLEFT]
: height - border_width[ST_SIDE_BOTTOM];
cogl_rectangle (x1, y1, x2, y2);
}
}
/* corners */
if (max_border_radius > 0 && paint_opacity > 0)
{
for (corner_id = 0; corner_id < 4; corner_id++)
{
if (node->corner_material[corner_id] == COGL_INVALID_HANDLE)
continue;
cogl_material_set_color4ub (node->corner_material[corner_id],
paint_opacity, paint_opacity,
paint_opacity, paint_opacity);
cogl_set_source (node->corner_material[corner_id]);
switch (corner_id)
{
case ST_CORNER_TOPLEFT:
cogl_rectangle_with_texture_coords (0, 0,
max_width_radius[ST_CORNER_TOPLEFT], max_width_radius[ST_CORNER_TOPLEFT],
0, 0, 0.5, 0.5);
break;
case ST_CORNER_TOPRIGHT:
cogl_rectangle_with_texture_coords (width - max_width_radius[ST_CORNER_TOPRIGHT], 0,
width, max_width_radius[ST_CORNER_TOPRIGHT],
0.5, 0, 1, 0.5);
break;
case ST_CORNER_BOTTOMRIGHT:
cogl_rectangle_with_texture_coords (width - max_width_radius[ST_CORNER_BOTTOMRIGHT], height - max_width_radius[ST_CORNER_BOTTOMRIGHT],
width, height,
0.5, 0.5, 1, 1);
break;
case ST_CORNER_BOTTOMLEFT:
cogl_rectangle_with_texture_coords (0, height - max_width_radius[ST_CORNER_BOTTOMLEFT],
max_width_radius[ST_CORNER_BOTTOMLEFT], height,
0, 0.5, 0.5, 1);
break;
}
}
}
/* background color */
alpha = paint_opacity * node->background_color.alpha / 255;
if (alpha > 0)
{
cogl_set_source_color4ub (node->background_color.red,
node->background_color.green,
node->background_color.blue,
alpha);
/* We add padding to each corner, so that all corners end up as if they
* had a border-radius of max_border_radius, which allows us to treat
* corners as uniform further on.
*/
for (corner_id = 0; corner_id < 4; corner_id++)
{
float verts[8];
int n_rects;
/* corner texture does not need padding */
if (max_border_radius == node->border_radius[corner_id])
continue;
n_rects = node->border_radius[corner_id] == 0 ? 1 : 2;
switch (corner_id)
{
case ST_CORNER_TOPLEFT:
verts[0] = border_width[ST_SIDE_LEFT];
verts[1] = MAX(node->border_radius[corner_id],
border_width[ST_SIDE_TOP]);
verts[2] = max_border_radius;
verts[3] = max_border_radius;
if (n_rects == 2)
{
verts[4] = MAX(node->border_radius[corner_id],
border_width[ST_SIDE_LEFT]);
verts[5] = border_width[ST_SIDE_TOP];
verts[6] = max_border_radius;
verts[7] = MAX(node->border_radius[corner_id],
border_width[ST_SIDE_TOP]);
}
break;
case ST_CORNER_TOPRIGHT:
verts[0] = width - max_border_radius;
verts[1] = MAX(node->border_radius[corner_id],
border_width[ST_SIDE_TOP]);
verts[2] = width - border_width[ST_SIDE_RIGHT];
verts[3] = max_border_radius;
if (n_rects == 2)
{
verts[4] = width - max_border_radius;
verts[5] = border_width[ST_SIDE_TOP];
verts[6] = width - MAX(node->border_radius[corner_id],
border_width[ST_SIDE_RIGHT]);
verts[7] = MAX(node->border_radius[corner_id],
border_width[ST_SIDE_TOP]);
}
break;
case ST_CORNER_BOTTOMRIGHT:
verts[0] = width - max_border_radius;
verts[1] = height - max_border_radius;
verts[2] = width - border_width[ST_SIDE_RIGHT];
verts[3] = height - MAX(node->border_radius[corner_id],
border_width[ST_SIDE_BOTTOM]);
if (n_rects == 2)
{
verts[4] = width - max_border_radius;
verts[5] = height - MAX(node->border_radius[corner_id],
border_width[ST_SIDE_BOTTOM]);
verts[6] = width - MAX(node->border_radius[corner_id],
border_width[ST_SIDE_RIGHT]);
verts[7] = height - border_width[ST_SIDE_BOTTOM];
}
break;
case ST_CORNER_BOTTOMLEFT:
verts[0] = border_width[ST_SIDE_LEFT];
verts[1] = height - max_border_radius;
verts[2] = max_border_radius;
verts[3] = height - MAX(node->border_radius[corner_id],
border_width[ST_SIDE_BOTTOM]);
if (n_rects == 2)
{
verts[4] = MAX(node->border_radius[corner_id],
border_width[ST_SIDE_LEFT]);
verts[5] = height - MAX(node->border_radius[corner_id],
border_width[ST_SIDE_BOTTOM]);
verts[6] = max_border_radius;
verts[7] = height - border_width[ST_SIDE_BOTTOM];
}
break;
}
cogl_rectangles (verts, n_rects);
}
/* Once we've drawn the borders and corners, if the corners are bigger
* then the border width, the remaining area is shaped like
*
* ########
* ##########
* ##########
* ########
*
* We draw it in at most 3 pieces - first the top and bottom if
* necessary, then the main rectangle
*/
if (max_border_radius > border_width[ST_SIDE_TOP])
cogl_rectangle (MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
border_width[ST_SIDE_TOP],
width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
max_border_radius);
if (max_border_radius > border_width[ST_SIDE_BOTTOM])
cogl_rectangle (MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
height - max_border_radius,
width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
height - border_width[ST_SIDE_BOTTOM]);
cogl_rectangle (border_width[ST_SIDE_LEFT],
MAX(border_width[ST_SIDE_TOP], max_border_radius),
width - border_width[ST_SIDE_RIGHT],
height - MAX(border_width[ST_SIDE_BOTTOM], max_border_radius));
}
}
static void
st_theme_node_paint_sliced_border_image (StThemeNode *node,
const ClutterActorBox *box,
guint8 paint_opacity)
{
gfloat ex, ey;
gfloat tx1, ty1, tx2, ty2;
gint border_left, border_right, border_top, border_bottom;
float img_width, img_height;
StBorderImage *border_image;
CoglHandle material;
border_image = st_theme_node_get_border_image (node);
g_assert (border_image != NULL);
st_border_image_get_borders (border_image,
&border_left, &border_right, &border_top, &border_bottom);
img_width = cogl_texture_get_width (node->border_slices_texture);
img_height = cogl_texture_get_height (node->border_slices_texture);
tx1 = border_left / img_width;
tx2 = (img_width - border_right) / img_width;
ty1 = border_top / img_height;
ty2 = (img_height - border_bottom) / img_height;
ex = node->alloc_width - border_right;
if (ex < 0)
ex = border_right; /* FIXME ? */
ey = node->alloc_height - border_bottom;
if (ey < 0)
ey = border_bottom; /* FIXME ? */
material = node->border_slices_material;
cogl_material_set_color4ub (material,
paint_opacity, paint_opacity, paint_opacity, paint_opacity);
cogl_set_source (material);
{
GLfloat rectangles[] =
{
/* top left corner */
0, 0, border_left, border_top,
0.0, 0.0,
tx1, ty1,
/* top middle */
border_left, 0, ex, border_top,
tx1, 0.0,
tx2, ty1,
/* top right */
ex, 0, node->alloc_width, border_top,
tx2, 0.0,
1.0, ty1,
/* mid left */
0, border_top, border_left, ey,
0.0, ty1,
tx1, ty2,
/* center */
border_left, border_top, ex, ey,
tx1, ty1,
tx2, ty2,
/* mid right */
ex, border_top, node->alloc_width, ey,
tx2, ty1,
1.0, ty2,
/* bottom left */
0, ey, border_left, node->alloc_height,
0.0, ty2,
tx1, 1.0,
/* bottom center */
border_left, ey, ex, node->alloc_height,
tx1, ty2,
tx2, 1.0,
/* bottom right */
ex, ey, node->alloc_width, node->alloc_height,
tx2, ty2,
1.0, 1.0
};
cogl_rectangles_with_texture_coords (rectangles, 9);
}
}
static void
st_theme_node_paint_outline (StThemeNode *node,
const ClutterActorBox *box,
guint8 paint_opacity)
{
float width, height;
int outline_width;
ClutterColor outline_color, effective_outline;
width = box->x2 - box->x1;
height = box->y2 - box->y1;
outline_width = st_theme_node_get_outline_width (node);
if (outline_width == 0)
return;
st_theme_node_get_outline_color (node, &outline_color);
over (&outline_color, &node->background_color, &effective_outline);
cogl_set_source_color4ub (effective_outline.red,
effective_outline.green,
effective_outline.blue,
paint_opacity * effective_outline.alpha / 255);
/* The outline is drawn just outside the border, which means just
* outside the allocation box. This means that in some situations
* involving clip_to_allocation or the screen edges, you won't be
* able to see the outline. In practice, it works well enough.
*/
/* NORTH */
cogl_rectangle (-outline_width, -outline_width,
width + outline_width, 0);
/* EAST */
cogl_rectangle (width, 0,
width + outline_width, height);
/* SOUTH */
cogl_rectangle (-outline_width, height,
width + outline_width, height + outline_width);
/* WEST */
cogl_rectangle (-outline_width, 0,
0, height);
}
void
st_theme_node_paint (StThemeNode *node,
const ClutterActorBox *box,
guint8 paint_opacity)
{
float width, height;
ClutterActorBox allocation;
/* Some things take an ActorBox, some things just width/height */
width = box->x2 - box->x1;
height = box->y2 - box->y1;
allocation.x1 = allocation.y1 = 0;
allocation.x2 = width;
allocation.y2 = height;
if (node->alloc_width != width || node->alloc_height != height)
st_theme_node_render_resources (node, width, height);
/* Rough notes about the relationship of borders and backgrounds in CSS3;
* see http://www.w3.org/TR/css3-background/ for more accurate details.
*
* - Things are drawn in 4 layers, from the bottom:
* Background color
* Background image
* Border color or border image
* Content
* - The background color, gradient and image extend to and are clipped by
* the edge of the border area, so will be rounded if the border is
* rounded. (CSS3 background-clip property modifies this)
* - The border image replaces what would normally be drawn by the border
* - The border image is not clipped by a rounded border-radius
* - The border radius rounds the background even if the border is
* zero width or a border image is being used.
*
* Deviations from the above as implemented here:
* - The combination of border image and a non-zero border radius is
* not supported; the background color will be drawn with square
* corners.
* - The background image is drawn above the border color, not below it.
* - We don't clip the background image to the (rounded) border area.
*/
if (node->box_shadow_material)
_st_paint_shadow_with_opacity (node->box_shadow,
node->box_shadow_material,
&allocation,
paint_opacity);
if (node->prerendered_material != COGL_INVALID_HANDLE ||
node->border_slices_material != COGL_INVALID_HANDLE)
{
if (node->prerendered_material != COGL_INVALID_HANDLE)
paint_material_with_opacity (node->prerendered_material, &allocation, paint_opacity);
if (node->border_slices_material != COGL_INVALID_HANDLE)
st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity);
}
else
{
st_theme_node_paint_borders (node, box, paint_opacity);
}
st_theme_node_paint_outline (node, box, paint_opacity);
if (node->background_texture != COGL_INVALID_HANDLE)
{
ClutterActorBox background_box;
get_background_position (node, &allocation, &background_box);
/* 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).
*/
if (node->background_shadow_material != COGL_INVALID_HANDLE)
_st_paint_shadow_with_opacity (node->background_image_shadow,
node->background_shadow_material,
&background_box,
paint_opacity);
paint_material_with_opacity (node->background_material, &background_box, paint_opacity);
}
}
/**
* st_theme_node_copy_cached_paint_state:
* @node: a #StThemeNode
* @other: a different #StThemeNode
*
* Copy cached painting state from @other to @node. This function can be used to
* optimize redrawing cached background images when the style on an element changess
* in a way that doesn't affect background drawing. This function must only be called
* if st_theme_node_paint_equal (node, other) returns %TRUE.
*/
void
st_theme_node_copy_cached_paint_state (StThemeNode *node,
StThemeNode *other)
{
int corner_id;
g_return_if_fail (ST_IS_THEME_NODE (node));
g_return_if_fail (ST_IS_THEME_NODE (other));
/* Check omitted for speed: */
/* g_return_if_fail (st_theme_node_paint_equal (node, other)); */
_st_theme_node_free_drawing_state (node);
node->alloc_width = other->alloc_width;
node->alloc_height = other->alloc_height;
if (other->background_shadow_material)
node->background_shadow_material = cogl_handle_ref (other->background_shadow_material);
if (other->box_shadow_material)
node->box_shadow_material = cogl_handle_ref (other->box_shadow_material);
if (other->background_texture)
node->background_texture = cogl_handle_ref (other->background_texture);
if (other->background_material)
node->background_material = cogl_handle_ref (other->background_material);
if (other->border_slices_texture)
node->border_slices_texture = cogl_handle_ref (other->border_slices_texture);
if (other->border_slices_material)
node->border_slices_material = cogl_handle_ref (other->border_slices_material);
if (other->prerendered_texture)
node->prerendered_texture = cogl_handle_ref (other->prerendered_texture);
if (other->prerendered_material)
node->prerendered_material = cogl_handle_ref (other->prerendered_material);
for (corner_id = 0; corner_id < 4; corner_id++)
if (other->corner_material[corner_id])
node->corner_material[corner_id] = cogl_handle_ref (other->corner_material[corner_id]);
}