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
This commit is contained in:
Florian Müllner 2010-10-01 03:58:59 +02:00
parent b33ebb450a
commit 3d2d396c09
4 changed files with 241 additions and 54 deletions

View File

@ -503,6 +503,8 @@ st_theme_node_render_gradient (StThemeNode *node)
void void
_st_theme_node_free_drawing_state (StThemeNode *node) _st_theme_node_free_drawing_state (StThemeNode *node)
{ {
int corner_id;
if (node->background_texture != COGL_INVALID_HANDLE) if (node->background_texture != COGL_INVALID_HANDLE)
cogl_handle_unref (node->background_texture); cogl_handle_unref (node->background_texture);
if (node->background_shadow_material != COGL_INVALID_HANDLE) if (node->background_shadow_material != COGL_INVALID_HANDLE)
@ -512,16 +514,25 @@ _st_theme_node_free_drawing_state (StThemeNode *node)
if (node->border_shadow_material != COGL_INVALID_HANDLE) if (node->border_shadow_material != COGL_INVALID_HANDLE)
cogl_handle_unref (node->border_shadow_material); 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); _st_theme_node_init_drawing_state (node);
} }
void void
_st_theme_node_init_drawing_state (StThemeNode *node) _st_theme_node_init_drawing_state (StThemeNode *node)
{ {
int corner_id;
node->background_texture = COGL_INVALID_HANDLE; node->background_texture = COGL_INVALID_HANDLE;
node->background_shadow_material = COGL_INVALID_HANDLE; node->background_shadow_material = COGL_INVALID_HANDLE;
node->border_shadow_material = COGL_INVALID_HANDLE; node->border_shadow_material = COGL_INVALID_HANDLE;
node->border_texture = 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, static void st_theme_node_paint_borders (StThemeNode *node,
@ -619,7 +630,14 @@ st_theme_node_render_resources (StThemeNode *node,
} }
} }
node->corner_texture = st_theme_node_lookup_corner (node, ST_CORNER_TOPLEFT); 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 static void
@ -656,8 +674,9 @@ st_theme_node_paint_borders (StThemeNode *node,
{ {
float width, height; float width, height;
int border_width; int border_width;
int border_radius; int max_border_radius = 0;
int max_width_radius; int max_width_radius[4];
int corner_id;
ClutterColor border_color; ClutterColor border_color;
CoglHandle material; CoglHandle material;
@ -665,14 +684,20 @@ st_theme_node_paint_borders (StThemeNode *node,
height = box->y2 - box->y1; height = box->y2 - box->y1;
get_arbitrary_border (node, &border_width, &border_color); get_arbitrary_border (node, &border_width, &border_color);
border_radius = node->border_radius[ST_CORNER_TOPLEFT]; for (corner_id = 0; corner_id < 4; corner_id++)
{
max_width_radius = MAX(border_width, border_radius); 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 */ /* borders */
if (border_width > 0) if (border_width > 0)
{ {
ClutterColor effective_border; ClutterColor effective_border;
gboolean skip_corner_1, skip_corner_2;
float x1, y1, x2, y2;
over (&border_color, &node->background_color, &effective_border); over (&border_color, &node->background_color, &effective_border);
@ -681,59 +706,91 @@ st_theme_node_paint_borders (StThemeNode *node,
effective_border.blue, effective_border.blue,
paint_opacity * effective_border.alpha / 255); paint_opacity * effective_border.alpha / 255);
if (border_radius > 0) /* skip corners */ /* NORTH */
{ skip_corner_1 = node->border_radius[ST_CORNER_TOPLEFT] > 0;
/* NORTH */ skip_corner_2 = node->border_radius[ST_CORNER_TOPRIGHT] > 0;
cogl_rectangle (max_width_radius, 0,
width - max_width_radius, border_width);
/* EAST */ x1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT] : 0;
cogl_rectangle (width - border_width, max_width_radius, y1 = 0;
width, height - max_width_radius); x2 = skip_corner_2 ? width - max_width_radius[ST_CORNER_TOPRIGHT] : width;
y2 = border_width;
cogl_rectangle (x1, y1, x2, y2);
/* SOUTH */ /* EAST */
cogl_rectangle (max_width_radius, height - border_width, skip_corner_1 = node->border_radius[ST_CORNER_TOPRIGHT] > 0;
width - max_width_radius, height); skip_corner_2 = node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
/* WEST */ x1 = width - border_width;
cogl_rectangle (0, max_width_radius, y1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT] : border_width;
border_width, height - max_width_radius); x2 = width;
} y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
else /* include corners */ : height - border_width;
{ cogl_rectangle (x1, y1, x2, y2);
/* NORTH */
cogl_rectangle (0, 0,
width, border_width);
/* EAST */ /* SOUTH */
cogl_rectangle (width - border_width, border_width, skip_corner_1 = node->border_radius[ST_CORNER_BOTTOMLEFT] > 0;
width, height - border_width); skip_corner_2 = node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
/* SOUTH */ x1 = skip_corner_1 ? max_width_radius[ST_CORNER_BOTTOMLEFT] : 0;
cogl_rectangle (0, height - border_width, y1 = height - border_width;
width, height); x2 = skip_corner_2 ? width - max_width_radius[ST_CORNER_BOTTOMRIGHT]
: width;
y2 = height;
cogl_rectangle (x1, y1, x2, y2);
/* WEST */ /* WEST */
cogl_rectangle (0, border_width, skip_corner_1 = node->border_radius[ST_CORNER_TOPLEFT] > 0;
border_width, height - border_width); 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 */ /* corners */
if (node->corner_texture != COGL_INVALID_HANDLE) if (max_border_radius > 0)
{ {
material = cogl_material_new (); material = cogl_material_new ();
cogl_material_set_layer (material, 0, node->corner_texture);
cogl_material_set_color4ub (material, cogl_material_set_color4ub (material,
paint_opacity, paint_opacity, paint_opacity, paint_opacity); paint_opacity, paint_opacity,
paint_opacity, paint_opacity);
cogl_set_source (material); cogl_set_source (material);
cogl_rectangle_with_texture_coords (0, 0, max_width_radius, max_width_radius, 0, 0, 0.5, 0.5); for (corner_id = 0; corner_id < 4; corner_id++)
cogl_rectangle_with_texture_coords (width - max_width_radius, 0, width, max_width_radius, 0.5, 0, 1, 0.5); {
cogl_rectangle_with_texture_coords (width - max_width_radius, height - max_width_radius, width, height, 0.5, 0.5, 1, 1); if (node->corner_texture[corner_id] == COGL_INVALID_HANDLE)
cogl_rectangle_with_texture_coords (0, height - max_width_radius, max_width_radius, height, 0, 0.5, 0.5, 1); 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); cogl_handle_unref (material);
} }
@ -743,7 +800,80 @@ st_theme_node_paint_borders (StThemeNode *node,
node->background_color.blue, node->background_color.blue,
paint_opacity * node->background_color.alpha / 255); paint_opacity * node->background_color.alpha / 255);
if (border_radius > border_width) /* 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 /* Once we've drawn the borders and corners, if the corners are bigger
* the the border width, the remaining area is shaped like * the the border width, the remaining area is shaped like
@ -756,14 +886,14 @@ st_theme_node_paint_borders (StThemeNode *node,
* We draw it in 3 pieces - first the top and bottom, then the main * We draw it in 3 pieces - first the top and bottom, then the main
* rectangle * rectangle
*/ */
cogl_rectangle (border_radius, border_width, cogl_rectangle (max_border_radius, border_width,
width - border_radius, border_radius); width - max_border_radius, max_border_radius);
cogl_rectangle (border_radius, height - border_radius, cogl_rectangle (max_border_radius, height - max_border_radius,
width - border_radius, height - border_width); width - max_border_radius, height - border_width);
} }
cogl_rectangle (border_width, max_width_radius, cogl_rectangle (border_width, MAX(border_width, max_border_radius),
width - border_width, height - max_width_radius); width - border_width, height - MAX(border_width, max_border_radius));
} }
static void static void
@ -1021,6 +1151,8 @@ void
st_theme_node_copy_cached_paint_state (StThemeNode *node, st_theme_node_copy_cached_paint_state (StThemeNode *node,
StThemeNode *other) StThemeNode *other)
{ {
int corner_id;
g_return_if_fail (ST_IS_THEME_NODE (node)); g_return_if_fail (ST_IS_THEME_NODE (node));
g_return_if_fail (ST_IS_THEME_NODE (other)); g_return_if_fail (ST_IS_THEME_NODE (other));
@ -1040,6 +1172,7 @@ st_theme_node_copy_cached_paint_state (StThemeNode *node,
node->background_texture = cogl_handle_ref (other->background_texture); node->background_texture = cogl_handle_ref (other->background_texture);
if (other->border_texture) if (other->border_texture)
node->border_texture = cogl_handle_ref (other->border_texture); node->border_texture = cogl_handle_ref (other->border_texture);
if (other->corner_texture) for (corner_id = 0; corner_id < 4; corner_id++)
node->corner_texture = cogl_handle_ref (other->corner_texture); if (other->corner_texture[corner_id])
node->corner_texture[corner_id] = cogl_handle_ref (other->corner_texture[corner_id]);
} }

View File

@ -78,7 +78,7 @@ struct _StThemeNode {
CoglHandle border_shadow_material; CoglHandle border_shadow_material;
CoglHandle background_texture; CoglHandle background_texture;
CoglHandle border_texture; CoglHandle border_texture;
CoglHandle corner_texture; CoglHandle corner_texture[4];
}; };
struct _StThemeNodeClass { struct _StThemeNodeClass {

View File

@ -3,6 +3,7 @@ EXTRA_DIST = run-test.sh.in
TEST_JS = \ TEST_JS = \
interactive/borders.js \ interactive/borders.js \
interactive/border-radius.js \
interactive/box-layout.js \ interactive/box-layout.js \
interactive/calendar.js \ interactive/calendar.js \
interactive/css-fonts.js \ interactive/css-fonts.js \

View File

@ -0,0 +1,53 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const UI = imports.testcommon.ui;
UI.init();
let stage = Clutter.Stage.get_default();
stage.width = 640;
stage.height = 480;
let vbox = new St.BoxLayout({ vertical: true,
width: stage.width,
height: stage.height,
style: 'padding: 10px;'
+ 'spacing: 20px;'
+ 'background: #ffee88;' });
let scroll = new St.ScrollView();
scroll.add_actor(vbox);
stage.add_actor(scroll);
function addTestCase(radii, useGradient) {
let background;
if (useGradient)
background = 'background-gradient-direction: vertical;'
+ 'background-gradient-start: white;'
+ 'background-gradient-end: gray;';
else
background = 'background: white;';
vbox.add(new St.Label({ text: "border-radius: " + radii + ";",
style: 'border: 1px solid black; '
+ 'border-radius: ' + radii + ';'
+ 'padding: 5px;' + background }),
{ x_fill: false });
}
// uniform backgrounds
addTestCase(" 0px 5px 10px 15px", false);
addTestCase(" 5px 10px 15px 0px", false);
addTestCase("10px 15px 0px 5px", false);
addTestCase("15px 0px 5px 10px", false);
// gradient backgrounds
addTestCase(" 0px 5px 10px 15px", true);
addTestCase(" 5px 10px 15px 0px", true);
addTestCase("10px 15px 0px 5px", true);
addTestCase("15px 0px 5px 10px", true);
stage.show();
Clutter.main();
stage.destroy();