css: add background-blend-mode support
CSS supports blend modes, in which a series of layers are merged together according to the given operation or set of operations. Support for blend modes landed on Cairo, which exposes all the commons and also the exquisites blend modes available. Adding support for blend modes, then, is just a matter of using the available Cairo operations. This patch adds the background-blend-mode CSS enum property, and adapts the background rendering code to blend the backgrounds using the available blend modes when they're set. https://bugzilla.gnome.org/show_bug.cgi?id=768305
This commit is contained in:
committed by
Matthias Clasen
parent
c8a74a1f50
commit
369db4a406
@@ -126,6 +126,67 @@ _gtk_css_border_style_value_get (const GtkCssValue *value)
|
||||
return value->value;
|
||||
}
|
||||
|
||||
/* GtkCssBlendMode */
|
||||
|
||||
static const GtkCssValueClass GTK_CSS_VALUE_BLEND_MODE = {
|
||||
gtk_css_value_enum_free,
|
||||
gtk_css_value_enum_compute,
|
||||
gtk_css_value_enum_equal,
|
||||
gtk_css_value_enum_transition,
|
||||
gtk_css_value_enum_print
|
||||
};
|
||||
|
||||
static GtkCssValue blend_mode_values[] = {
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_COLOR, "color" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_COLOR_BURN, "color-burn" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_COLOR_DODGE, "color-dodge" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_DARKEN, "darken" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_DIFFERENCE, "difference" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_EXCLUSION, "exclusion" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_HARD_LIGHT, "hard-light" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_HUE, "hue" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_LIGHTEN, "lighten" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_LUMINOSITY, "luminosity" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_MULTIPLY, "multiply" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_NORMAL, "normal" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_OVERLAY, "overlay" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_SATURATE, "saturate" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_SCREEN, "screen" },
|
||||
{ >K_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_SOFT_LIGHT, "soft-light" }
|
||||
};
|
||||
|
||||
GtkCssValue *
|
||||
_gtk_css_blend_mode_value_new (GtkCssBlendMode blend_mode)
|
||||
{
|
||||
g_return_val_if_fail (blend_mode < G_N_ELEMENTS (blend_mode_values), NULL);
|
||||
|
||||
return _gtk_css_value_ref (&blend_mode_values[blend_mode]);
|
||||
}
|
||||
|
||||
GtkCssValue *
|
||||
_gtk_css_blend_mode_value_try_parse (GtkCssParser *parser)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_return_val_if_fail (parser != NULL, NULL);
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (blend_mode_values); i++)
|
||||
{
|
||||
if (_gtk_css_parser_try (parser, blend_mode_values[i].name, TRUE))
|
||||
return _gtk_css_value_ref (&blend_mode_values[i]);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GtkCssBlendMode
|
||||
_gtk_css_blend_mode_value_get (const GtkCssValue *value)
|
||||
{
|
||||
g_return_val_if_fail (value->class == >K_CSS_VALUE_BLEND_MODE, GTK_CSS_BLEND_MODE_NORMAL);
|
||||
|
||||
return value->value;
|
||||
}
|
||||
|
||||
/* GtkCssFontSize */
|
||||
|
||||
static double
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GtkCssValue * _gtk_css_blend_mode_value_new (GtkCssBlendMode blend_mode);
|
||||
GtkCssValue * _gtk_css_blend_mode_value_try_parse (GtkCssParser *parser);
|
||||
GtkCssBlendMode _gtk_css_blend_mode_value_get (const GtkCssValue *value);
|
||||
|
||||
GtkCssValue * _gtk_css_border_style_value_new (GtkBorderStyle border_style);
|
||||
GtkCssValue * _gtk_css_border_style_value_try_parse (GtkCssParser *parser);
|
||||
GtkBorderStyle _gtk_css_border_style_value_get (const GtkCssValue *value);
|
||||
|
||||
@@ -985,6 +985,24 @@ parse_border_width (GtkCssStyleProperty *property,
|
||||
| GTK_CSS_PARSE_LENGTH);
|
||||
}
|
||||
|
||||
static GtkCssValue *
|
||||
blend_mode_value_parse_one (GtkCssParser *parser)
|
||||
{
|
||||
GtkCssValue *value = _gtk_css_blend_mode_value_try_parse (parser);
|
||||
|
||||
if (value == NULL)
|
||||
_gtk_css_parser_error (parser, "unknown value for property");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static GtkCssValue *
|
||||
blend_mode_value_parse (GtkCssStyleProperty *property,
|
||||
GtkCssParser *parser)
|
||||
{
|
||||
return _gtk_css_array_value_parse (parser, blend_mode_value_parse_one);
|
||||
}
|
||||
|
||||
static GtkCssValue *
|
||||
background_repeat_value_parse_one (GtkCssParser *parser)
|
||||
{
|
||||
@@ -1565,6 +1583,16 @@ _gtk_css_style_property_init_properties (void)
|
||||
background_image_value_assign,
|
||||
_gtk_css_array_value_new (_gtk_css_image_value_new (NULL)));
|
||||
|
||||
gtk_css_style_property_register ("background-blend-mode",
|
||||
GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE,
|
||||
G_TYPE_NONE,
|
||||
0,
|
||||
GTK_CSS_AFFECTS_BACKGROUND,
|
||||
blend_mode_value_parse,
|
||||
NULL,
|
||||
NULL,
|
||||
_gtk_css_array_value_new (_gtk_css_blend_mode_value_new (GTK_CSS_BLEND_MODE_NORMAL)));
|
||||
|
||||
gtk_css_style_property_register ("border-image-source",
|
||||
GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE,
|
||||
CAIRO_GOBJECT_TYPE_PATTERN,
|
||||
|
||||
@@ -22,6 +22,46 @@
|
||||
#include "gtkcssnumbervalueprivate.h"
|
||||
#include "gtkstylecontextprivate.h"
|
||||
|
||||
cairo_operator_t
|
||||
_gtk_css_blend_mode_get_operator (GtkCssBlendMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case GTK_CSS_BLEND_MODE_COLOR:
|
||||
return CAIRO_OPERATOR_HSL_COLOR;
|
||||
case GTK_CSS_BLEND_MODE_COLOR_BURN:
|
||||
return CAIRO_OPERATOR_COLOR_BURN;
|
||||
case GTK_CSS_BLEND_MODE_COLOR_DODGE:
|
||||
return CAIRO_OPERATOR_COLOR_DODGE;
|
||||
case GTK_CSS_BLEND_MODE_DARKEN:
|
||||
return CAIRO_OPERATOR_DARKEN;
|
||||
case GTK_CSS_BLEND_MODE_DIFFERENCE:
|
||||
return CAIRO_OPERATOR_DIFFERENCE;
|
||||
case GTK_CSS_BLEND_MODE_EXCLUSION:
|
||||
return CAIRO_OPERATOR_EXCLUSION;
|
||||
case GTK_CSS_BLEND_MODE_HARD_LIGHT:
|
||||
return CAIRO_OPERATOR_HARD_LIGHT;
|
||||
case GTK_CSS_BLEND_MODE_HUE:
|
||||
return CAIRO_OPERATOR_HSL_HUE;
|
||||
case GTK_CSS_BLEND_MODE_LIGHTEN:
|
||||
return CAIRO_OPERATOR_LIGHTEN;
|
||||
case GTK_CSS_BLEND_MODE_LUMINOSITY:
|
||||
return CAIRO_OPERATOR_HSL_LUMINOSITY;
|
||||
case GTK_CSS_BLEND_MODE_MULTIPLY:
|
||||
return CAIRO_OPERATOR_MULTIPLY;
|
||||
case GTK_CSS_BLEND_MODE_OVERLAY:
|
||||
return CAIRO_OPERATOR_OVERLAY;
|
||||
case GTK_CSS_BLEND_MODE_SATURATE:
|
||||
return CAIRO_OPERATOR_SATURATE;
|
||||
case GTK_CSS_BLEND_MODE_SCREEN:
|
||||
return CAIRO_OPERATOR_SCREEN;
|
||||
|
||||
case GTK_CSS_BLEND_MODE_NORMAL:
|
||||
default:
|
||||
return CAIRO_OPERATOR_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
GtkCssChange
|
||||
_gtk_css_change_for_sibling (GtkCssChange match)
|
||||
{
|
||||
|
||||
@@ -198,6 +198,7 @@ enum { /*< skip >*/
|
||||
GTK_CSS_PROPERTY_OUTLINE_COLOR,
|
||||
GTK_CSS_PROPERTY_BACKGROUND_REPEAT,
|
||||
GTK_CSS_PROPERTY_BACKGROUND_IMAGE,
|
||||
GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE,
|
||||
GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE,
|
||||
GTK_CSS_PROPERTY_BORDER_IMAGE_REPEAT,
|
||||
GTK_CSS_PROPERTY_BORDER_IMAGE_SLICE,
|
||||
@@ -230,6 +231,25 @@ enum { /*< skip >*/
|
||||
GTK_CSS_PROPERTY_N_PROPERTIES
|
||||
};
|
||||
|
||||
typedef enum /*< skip >*/ {
|
||||
GTK_CSS_BLEND_MODE_COLOR,
|
||||
GTK_CSS_BLEND_MODE_COLOR_BURN,
|
||||
GTK_CSS_BLEND_MODE_COLOR_DODGE,
|
||||
GTK_CSS_BLEND_MODE_DARKEN,
|
||||
GTK_CSS_BLEND_MODE_DIFFERENCE,
|
||||
GTK_CSS_BLEND_MODE_EXCLUSION,
|
||||
GTK_CSS_BLEND_MODE_HARD_LIGHT,
|
||||
GTK_CSS_BLEND_MODE_HUE,
|
||||
GTK_CSS_BLEND_MODE_LIGHTEN,
|
||||
GTK_CSS_BLEND_MODE_LUMINOSITY,
|
||||
GTK_CSS_BLEND_MODE_MULTIPLY,
|
||||
GTK_CSS_BLEND_MODE_NORMAL,
|
||||
GTK_CSS_BLEND_MODE_OVERLAY,
|
||||
GTK_CSS_BLEND_MODE_SATURATE,
|
||||
GTK_CSS_BLEND_MODE_SCREEN,
|
||||
GTK_CSS_BLEND_MODE_SOFT_LIGHT
|
||||
} GtkCssBlendMode;
|
||||
|
||||
typedef enum /*< skip >*/ {
|
||||
GTK_CSS_IMAGE_BUILTIN_NONE,
|
||||
GTK_CSS_IMAGE_BUILTIN_CHECK,
|
||||
@@ -372,6 +392,8 @@ typedef enum /*< skip >*/ {
|
||||
GTK_CSS_MS,
|
||||
} GtkCssUnit;
|
||||
|
||||
cairo_operator_t _gtk_css_blend_mode_get_operator (GtkCssBlendMode mode);
|
||||
|
||||
GtkCssChange _gtk_css_change_for_sibling (GtkCssChange match);
|
||||
GtkCssChange _gtk_css_change_for_child (GtkCssChange match);
|
||||
|
||||
|
||||
@@ -72,10 +72,45 @@ _gtk_theming_background_paint_color (GtkThemingBackground *bg,
|
||||
cairo_fill (cr);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_gtk_theming_background_needs_push_group (GtkCssStyle *style)
|
||||
{
|
||||
const GdkRGBA *bg_color;
|
||||
GtkCssValue *background_color;
|
||||
GtkCssValue *blend_modes;
|
||||
gint i;
|
||||
|
||||
background_color = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_COLOR);
|
||||
bg_color = _gtk_css_rgba_value_get_rgba (background_color);
|
||||
|
||||
/* An opaque background-color means we don't need to push the group */
|
||||
if (bg_color->alpha == 1)
|
||||
return FALSE;
|
||||
|
||||
blend_modes = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE);
|
||||
|
||||
/*
|
||||
* If we have any blend mode different than NORMAL, we'll need to
|
||||
* push a group in order to correctly apply the blend modes.
|
||||
*/
|
||||
for (i = _gtk_css_array_value_get_n_values (blend_modes); i > 0; i--)
|
||||
{
|
||||
GtkCssBlendMode blend_mode;
|
||||
|
||||
blend_mode = _gtk_css_blend_mode_value_get (_gtk_css_array_value_get_nth (blend_modes, i - 1));
|
||||
|
||||
if (blend_mode != GTK_CSS_BLEND_MODE_NORMAL)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_gtk_theming_background_paint_layer (GtkThemingBackground *bg,
|
||||
guint idx,
|
||||
cairo_t *cr)
|
||||
cairo_t *cr,
|
||||
GtkCssBlendMode blend_mode)
|
||||
{
|
||||
GtkCssRepeatStyle hrepeat, vrepeat;
|
||||
const GtkCssValue *pos, *repeat;
|
||||
@@ -134,6 +169,13 @@ _gtk_theming_background_paint_layer (GtkThemingBackground *bg,
|
||||
|
||||
cairo_translate (cr, origin->box.x, origin->box.y);
|
||||
|
||||
/*
|
||||
* Apply the blend mode, if any.
|
||||
*/
|
||||
if (G_UNLIKELY (_gtk_css_blend_mode_get_operator (blend_mode) != cairo_get_operator (cr)))
|
||||
cairo_set_operator (cr, _gtk_css_blend_mode_get_operator (blend_mode));
|
||||
|
||||
|
||||
if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT && vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
|
||||
{
|
||||
cairo_translate (cr,
|
||||
@@ -249,6 +291,12 @@ _gtk_theming_background_paint_layer (GtkThemingBackground *bg,
|
||||
cairo_fill (cr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Since this cairo_t can be shared with other widgets,
|
||||
* we must reset the operator after all the backgrounds
|
||||
* are properly rendered.
|
||||
*/
|
||||
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
|
||||
|
||||
cairo_restore (cr);
|
||||
}
|
||||
@@ -304,10 +352,14 @@ gtk_css_style_render_background (GtkCssStyle *style,
|
||||
GtkThemingBackground bg;
|
||||
gint idx;
|
||||
GtkCssValue *background_image;
|
||||
GtkCssValue *blend_modes;
|
||||
GtkCssValue *box_shadow;
|
||||
const GdkRGBA *bg_color;
|
||||
gboolean needs_push_group;
|
||||
gint number_of_layers;
|
||||
|
||||
background_image = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_IMAGE);
|
||||
blend_modes = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE);
|
||||
bg_color = _gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_COLOR));
|
||||
box_shadow = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BOX_SHADOW);
|
||||
|
||||
@@ -324,6 +376,22 @@ gtk_css_style_render_background (GtkCssStyle *style,
|
||||
cairo_save (cr);
|
||||
cairo_translate (cr, x, y);
|
||||
|
||||
/*
|
||||
* When we have a blend mode set for the background, we cannot blend the current
|
||||
* widget's drawing with whatever the content that the Cairo context may have.
|
||||
* Because of that, push the drawing to a new group before drawing the background
|
||||
* layers, and paint the resulting image back after.
|
||||
*/
|
||||
needs_push_group = _gtk_theming_background_needs_push_group (style);
|
||||
|
||||
if (needs_push_group)
|
||||
{
|
||||
cairo_save (cr);
|
||||
cairo_rectangle (cr, x, y, width, height);
|
||||
cairo_clip (cr);
|
||||
cairo_push_group (cr);
|
||||
}
|
||||
|
||||
/* Outset shadows */
|
||||
_gtk_css_shadows_value_paint_box (box_shadow,
|
||||
cr,
|
||||
@@ -332,9 +400,15 @@ gtk_css_style_render_background (GtkCssStyle *style,
|
||||
|
||||
_gtk_theming_background_paint_color (&bg, cr, bg_color, background_image);
|
||||
|
||||
for (idx = _gtk_css_array_value_get_n_values (background_image) - 1; idx >= 0; idx--)
|
||||
number_of_layers = _gtk_css_array_value_get_n_values (background_image);
|
||||
|
||||
for (idx = number_of_layers - 1; idx >= 0; idx--)
|
||||
{
|
||||
_gtk_theming_background_paint_layer (&bg, idx, cr);
|
||||
GtkCssBlendMode blend_mode;
|
||||
|
||||
blend_mode = _gtk_css_blend_mode_value_get (_gtk_css_array_value_get_nth (blend_modes, idx));
|
||||
|
||||
_gtk_theming_background_paint_layer (&bg, idx, cr, blend_mode);
|
||||
}
|
||||
|
||||
/* Inset shadows */
|
||||
@@ -343,6 +417,14 @@ gtk_css_style_render_background (GtkCssStyle *style,
|
||||
&bg.boxes[GTK_CSS_AREA_PADDING_BOX],
|
||||
TRUE);
|
||||
|
||||
/* Paint back the resulting surface */
|
||||
if (needs_push_group)
|
||||
{
|
||||
cairo_pop_group_to_source (cr);
|
||||
cairo_paint (cr);
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user