diff --git a/gtk/gtkcssenumvalue.c b/gtk/gtkcssenumvalue.c index fb480fc2db..276f9dd530 100644 --- a/gtk/gtkcssenumvalue.c +++ b/gtk/gtkcssenumvalue.c @@ -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 diff --git a/gtk/gtkcssenumvalueprivate.h b/gtk/gtkcssenumvalueprivate.h index f8b37c2d41..deb145cb2e 100644 --- a/gtk/gtkcssenumvalueprivate.h +++ b/gtk/gtkcssenumvalueprivate.h @@ -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); diff --git a/gtk/gtkcssstylepropertyimpl.c b/gtk/gtkcssstylepropertyimpl.c index 138c50fa15..4f191e6d2e 100644 --- a/gtk/gtkcssstylepropertyimpl.c +++ b/gtk/gtkcssstylepropertyimpl.c @@ -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, diff --git a/gtk/gtkcsstypes.c b/gtk/gtkcsstypes.c index 45e6caf8ee..9a701da480 100644 --- a/gtk/gtkcsstypes.c +++ b/gtk/gtkcsstypes.c @@ -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) { diff --git a/gtk/gtkcsstypesprivate.h b/gtk/gtkcsstypesprivate.h index 3f5ce93f23..59f392a032 100644 --- a/gtk/gtkcsstypesprivate.h +++ b/gtk/gtkcsstypesprivate.h @@ -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); diff --git a/gtk/gtkrenderbackground.c b/gtk/gtkrenderbackground.c index a9a80f1636..b41d3a5876 100644 --- a/gtk/gtkrenderbackground.c +++ b/gtk/gtkrenderbackground.c @@ -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); }