gnome-shell/src/st/st-theme-node-drawing.c
Owen W. Taylor 3138b20b11 Skip drawing transparent borders and backgrounds
We were always drawing the border and background of each
StThemeNode, even if they were transparent. The simple
optimization of checking the alpha provides a significant
performance boost (in a quick test, it increased the
overviewFpsSubsequent metric in the core performance test
from 28fps to 35fps).

https://bugzilla.gnome.org/show_bug.cgi?id=634752
2010-11-13 10:12:40 -05:00

1186 lines
41 KiB
C

/* -*- 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <math.h>
#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 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);
/* TODO support nonuniform border widths */
if (corner->border_width_1 < corner->radius)
{
double internal_radius = 0.5 * (1.0 - (double) corner->border_width_1 / corner->radius);
if (corner->border_width_1 != 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.);
cairo_arc (cr, 0.5, 0.5, internal_radius, 0, 2 * M_PI);
cairo_fill (cr);
}
else
{
double radius;
radius = (gdouble)corner->radius / corner->border_width_1;
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 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;
switch (corner_id)
{
case ST_CORNER_TOPLEFT:
corner.border_width_1 = node->border_width[ST_SIDE_TOP];
corner.border_width_2 = node->border_width[ST_SIDE_LEFT];
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:
corner.border_width_1 = node->border_width[ST_SIDE_TOP];
corner.border_width_2 = node->border_width[ST_SIDE_RIGHT];
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:
corner.border_width_1 = node->border_width[ST_SIDE_BOTTOM];
corner.border_width_2 = node->border_width[ST_SIDE_RIGHT];
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:
corner.border_width_1 = node->border_width[ST_SIDE_BOTTOM];
corner.border_width_2 = node->border_width[ST_SIDE_LEFT];
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 widths and/or colors.
*/
static gboolean
get_arbitrary_border (StThemeNode *node,
int *width,
ClutterColor *color)
{
int w;
w = st_theme_node_get_border_width (node, ST_SIDE_TOP);
if (w > 0)
{
if (width)
*width = w;
if (color)
st_theme_node_get_border_color (node, ST_SIDE_TOP, color);
return TRUE;
}
if (width)
*width = 0;
return FALSE;
}
static CoglHandle
st_theme_node_render_gradient (StThemeNode *node)
{
CoglHandle texture;
int radius[4], i;
cairo_t *cr;
cairo_surface_t *surface;
cairo_pattern_t *pattern;
gboolean round_border = FALSE;
ClutterColor border_color;
int border_width;
guint rowstride;
guchar *data;
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 and widths */
get_arbitrary_border (node, &border_width, &border_color);
for (i = 0; i < 4; i++)
{
radius[i] = st_theme_node_get_border_radius (node, i);
if (radius[i] > 0)
round_border = TRUE;
}
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.);
if (round_border)
{
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])
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])
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);
}
else
cairo_rectangle (cr, 0, 0, node->alloc_width, node->alloc_height);
if (border_width > 0)
{
cairo_path_t *path;
path = cairo_copy_path (cr);
cairo_set_source_rgba (cr,
border_color.red / 255.,
border_color.green / 255.,
border_color.blue / 255.,
border_color.alpha / 255.);
cairo_fill (cr);
cairo_translate (cr, border_width, border_width);
cairo_scale (cr,
(gdouble)(node->alloc_width - 2 * border_width) / node->alloc_width,
(gdouble)(node->alloc_height - 2 * border_width) / node->alloc_height);
cairo_append_path (cr, path);
cairo_path_destroy (path);
}
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_texture != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_texture);
if (node->border_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_material);
if (node->border_shadow_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_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->border_shadow_material = COGL_INVALID_HANDLE;
node->border_texture = COGL_INVALID_HANDLE;
node->border_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 *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);
shadow_spec = st_theme_node_get_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_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename);
}
else if (node->background_gradient_type != ST_GRADIENT_NONE)
{
node->border_texture = st_theme_node_render_gradient (node);
}
if (node->border_texture)
node->border_material = _st_create_texture_material (node->border_texture);
else
node->border_material = COGL_INVALID_HANDLE;
if (shadow_spec)
{
if (node->border_texture != COGL_INVALID_HANDLE)
node->border_shadow_material = _st_create_shadow_material (shadow_spec,
node->border_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->border_shadow_material = _st_create_shadow_material (shadow_spec,
buffer);
}
cogl_handle_unref (buffer);
}
}
background_image = st_theme_node_get_background_image (node);
if (background_image != NULL)
{
node->background_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image);
node->background_material = _st_create_texture_material (node->background_texture);
if (shadow_spec)
{
node->background_shadow_material = _st_create_shadow_material (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;
int max_border_radius = 0;
int max_width_radius[4];
int corner_id;
ClutterColor border_color;
guint8 alpha;
width = box->x2 - box->x1;
height = box->y2 - box->y1;
get_arbitrary_border (node, &border_width, &border_color);
for (corner_id = 0; corner_id < 4; corner_id++)
{
if (node->border_radius[corner_id] > max_border_radius)
max_border_radius = node->border_radius[corner_id];
max_width_radius[corner_id] = MAX(border_width,
node->border_radius[corner_id]);
}
/* borders */
if (border_width > 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;
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;
y1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT] : border_width;
x2 = width;
y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
: height - border_width;
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;
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;
x2 = border_width;
y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMLEFT]
: height - border_width;
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;
verts[1] = max_width_radius[ST_CORNER_TOPLEFT];
verts[2] = max_border_radius;
verts[3] = max_border_radius;
if (n_rects == 2)
{
verts[4] = max_width_radius[ST_CORNER_TOPLEFT];
verts[5] = border_width;
verts[6] = max_border_radius;
verts[7] = max_width_radius[ST_CORNER_TOPLEFT];
}
break;
case ST_CORNER_TOPRIGHT:
verts[0] = width - max_border_radius;
verts[1] = max_width_radius[ST_CORNER_TOPRIGHT];
verts[2] = width - border_width;
verts[3] = max_border_radius;
if (n_rects == 2)
{
verts[4] = width - max_border_radius;
verts[5] = border_width;
verts[6] = width - max_width_radius[ST_CORNER_TOPRIGHT];
verts[7] = max_width_radius[ST_CORNER_TOPRIGHT];
}
break;
case ST_CORNER_BOTTOMRIGHT:
verts[0] = width - max_border_radius;
verts[1] = height - max_border_radius;
verts[2] = width - border_width;
verts[3] = height - max_width_radius[ST_CORNER_BOTTOMRIGHT];
if (n_rects == 2)
{
verts[4] = width - max_border_radius;
verts[5] = height - max_width_radius[ST_CORNER_BOTTOMRIGHT];
verts[6] = width - max_width_radius[ST_CORNER_BOTTOMRIGHT];
verts[7] = height - border_width;
}
break;
case ST_CORNER_BOTTOMLEFT:
verts[0] = border_width;
verts[1] = height - max_border_radius;
verts[2] = max_border_radius;
verts[3] = height - max_width_radius[ST_CORNER_BOTTOMLEFT];
if (n_rects == 2)
{
verts[4] = max_width_radius[ST_CORNER_BOTTOMLEFT];
verts[5] = height - max_width_radius[ST_CORNER_BOTTOMLEFT];
verts[6] = max_border_radius;
verts[7] = height - border_width;
}
break;
}
cogl_rectangles (verts, n_rects);
}
if (max_border_radius > border_width)
{
/* Once we've drawn the borders and corners, if the corners are bigger
* the the border width, the remaining area is shaped like
*
* ########
* ##########
* ##########
* ########
*
* We draw it in 3 pieces - first the top and bottom, then the main
* rectangle
*/
cogl_rectangle (max_border_radius, border_width,
width - max_border_radius, max_border_radius);
cogl_rectangle (max_border_radius, height - max_border_radius,
width - max_border_radius, height - border_width);
}
cogl_rectangle (border_width, MAX(border_width, max_border_radius),
width - border_width, height - MAX(border_width, 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_texture);
img_height = cogl_texture_get_height (node->border_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_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:
* - Nonuniform border widths combined with a non-zero border radius result
* in the border radius being ignored
* - The combination of border image and a non-zero border radius is
* not supported; the background color will be drawn with square
* corners.
* - The combination of border image and a background gradient is not
* supported; the background will be drawn as a solid color
* - The background image is drawn above the border color or image,
* not below it.
* - We don't clip the background image to the (rounded) border area.
*
* The first three allow us to always draw with no more than a single
* border_image and a single background image above it.
*/
if (node->border_shadow_material)
_st_paint_shadow_with_opacity (node->shadow,
node->border_shadow_material,
&allocation,
paint_opacity);
if (node->border_material != COGL_INVALID_HANDLE)
{
/* Gradients and border images are mutually exclusive at this time */
if (node->background_gradient_type != ST_GRADIENT_NONE)
paint_material_with_opacity (node->border_material, &allocation, paint_opacity);
else
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->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->border_shadow_material)
node->border_shadow_material = cogl_handle_ref (other->border_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_texture)
node->border_texture = cogl_handle_ref (other->border_texture);
if (other->border_material)
node->border_material = cogl_handle_ref (other->border_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]);
}