gnome-shell/src/st/st-theme-node-drawing.c
Florian Müllner 3d2d396c09 st-theme-node: Support non-uniform border-radii
Non-uniform border-radii are already supported when using a gradient
background, this patch adds support for solid colors as well.

The currently applied technique of using corner textures and filling
the remaining area with rectangles is extended, so that each corner is
padded with rectangles to the size of the largest corner.

Add border-radius.js to test cases, to test non-uniform border-radii
with both solid color and gradient backgrounds.

https://bugzilla.gnome.org/show_bug.cgi?id=631091
2010-10-05 00:18:49 +02:00

1179 lines
40 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* Drawing for StWidget.
Copyright (C) 2009,2010 Red Hat, Inc.
Contains code derived from:
rectangle.c: Rounded rectangle.
Copyright (C) 2008 litl, LLC.
st-shadow-texture.c: a class for creating soft shadow texture
Copyright (C) 2009 Florian Müllner <fmuellner@src.gnome.org>
st-texture-frame.h: Expandible texture actor
Copyright 2007 OpenedHand
Copyright 2009 Intel Corporation.
The St is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The St is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the St; see the file COPYING.LIB.
If not, write to the Free Software Foundation, Inc., 59 Temple Place -
Suite 330, Boston, MA 02111-1307, USA.
*/
#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_texture (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_texture (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;
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;
}
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);
g_free (key);
return texture;
}
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_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_shadow_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_shadow_material);
for (corner_id = 0; corner_id < 4; corner_id++)
if (node->corner_texture[corner_id] != COGL_INVALID_HANDLE)
cogl_handle_unref (node->corner_texture[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_shadow_material = COGL_INVALID_HANDLE;
node->border_shadow_material = COGL_INVALID_HANDLE;
node->border_texture = COGL_INVALID_HANDLE;
for (corner_id = 0; corner_id < 4; corner_id++)
node->corner_texture[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 (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);
if (shadow_spec)
{
node->background_shadow_material = _st_create_shadow_material (shadow_spec,
node->background_texture);
}
}
node->corner_texture[ST_CORNER_TOPLEFT] =
st_theme_node_lookup_corner (node, ST_CORNER_TOPLEFT);
node->corner_texture[ST_CORNER_TOPRIGHT] =
st_theme_node_lookup_corner (node, ST_CORNER_TOPRIGHT);
node->corner_texture[ST_CORNER_BOTTOMRIGHT] =
st_theme_node_lookup_corner (node, ST_CORNER_BOTTOMRIGHT);
node->corner_texture[ST_CORNER_BOTTOMLEFT] =
st_theme_node_lookup_corner (node, ST_CORNER_BOTTOMLEFT);
}
static void
paint_texture_with_opacity (CoglHandle texture,
ClutterActorBox *box,
guint8 paint_opacity)
{
if (paint_opacity == 255)
{
/* Minor: optimization use the default material if we can */
cogl_set_source_texture (texture);
cogl_rectangle (box->x1, box->y1, box->x2, box->y2);
return;
}
CoglHandle material;
material = cogl_material_new ();
cogl_material_set_layer (material, 0, texture);
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);
cogl_handle_unref (material);
}
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;
CoglHandle material;
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);
cogl_set_source_color4ub (effective_border.red,
effective_border.green,
effective_border.blue,
paint_opacity * effective_border.alpha / 255);
/* 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)
{
material = cogl_material_new ();
cogl_material_set_color4ub (material,
paint_opacity, paint_opacity,
paint_opacity, paint_opacity);
cogl_set_source (material);
for (corner_id = 0; corner_id < 4; corner_id++)
{
if (node->corner_texture[corner_id] == COGL_INVALID_HANDLE)
continue;
cogl_material_set_layer (material,
0, node->corner_texture[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;
}
}
cogl_handle_unref (material);
}
/* background color */
cogl_set_source_color4ub (node->background_color.red,
node->background_color.green,
node->background_color.blue,
paint_opacity * node->background_color.alpha / 255);
/* 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 = cogl_material_new ();
cogl_material_set_layer (material, 0, node->border_texture);
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);
}
cogl_handle_unref (material);
}
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_texture != COGL_INVALID_HANDLE)
{
/* Gradients and border images are mutually exclusive at this time */
if (node->background_gradient_type != ST_GRADIENT_NONE)
paint_texture_with_opacity (node->border_texture, &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_texture_with_opacity (node->background_texture, &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->border_texture)
node->border_texture = cogl_handle_ref (other->border_texture);
for (corner_id = 0; corner_id < 4; corner_id++)
if (other->corner_texture[corner_id])
node->corner_texture[corner_id] = cogl_handle_ref (other->corner_texture[corner_id]);
}