/* GTK - The GIMP Toolkit * Copyright (C) 2011 Red Hat, Inc. * * Author: Cosimo Cecchi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include "gtkcssshadowvalueprivate.h" #include "gtkcsscolorvalueprivate.h" #include "gtkcssnumbervalueprivate.h" #include "gtkcsscolorvalueprivate.h" #include "gtksnapshotprivate.h" #include "gtkpango.h" #include "gsk/gskcairoblurprivate.h" #include "gsk/gskroundedrectprivate.h" #include typedef struct { guint inset :1; GtkCssValue *hoffset; GtkCssValue *voffset; GtkCssValue *radius; GtkCssValue *spread; GtkCssValue *color; } ShadowValue; struct _GtkCssValue { GTK_CSS_VALUE_BASE guint is_filter : 1; /* values stored in radius are std_dev, for drop-shadow */ guint n_shadows; ShadowValue shadows[1]; }; static GtkCssValue * gtk_css_shadow_value_new (ShadowValue *shadows, guint n_shadows, gboolean is_filter); static void shadow_value_for_transition (ShadowValue *result, gboolean inset) { result->inset = inset; result->hoffset = _gtk_css_number_value_new (0, GTK_CSS_PX); result->voffset = _gtk_css_number_value_new (0, GTK_CSS_PX); result->radius = _gtk_css_number_value_new (0, GTK_CSS_PX); result->spread = _gtk_css_number_value_new (0, GTK_CSS_PX); result->color = gtk_css_color_value_new_transparent (); } static void shadow_value_unref (const ShadowValue *shadow) { _gtk_css_value_unref (shadow->hoffset); _gtk_css_value_unref (shadow->voffset); _gtk_css_value_unref (shadow->spread); _gtk_css_value_unref (shadow->radius); _gtk_css_value_unref (shadow->color); } static gboolean shadow_value_transition (const ShadowValue *start, const ShadowValue *end, guint property_id, double progress, ShadowValue *result) { if (start->inset != end->inset) return FALSE; result->inset = start->inset; result->hoffset = _gtk_css_value_transition (start->hoffset, end->hoffset, property_id, progress); result->voffset = _gtk_css_value_transition (start->voffset, end->voffset, property_id, progress); result->radius = _gtk_css_value_transition (start->radius, end->radius, property_id, progress); result->spread = _gtk_css_value_transition (start->spread, end->spread, property_id, progress); result->color = _gtk_css_value_transition (start->color, end->color, property_id, progress); return TRUE; } static void gtk_css_value_shadow_free (GtkCssValue *value) { guint i; for (i = 0; i < value->n_shadows; i ++) { const ShadowValue *shadow = &value->shadows[i]; shadow_value_unref (shadow); } g_slice_free1 (sizeof (GtkCssValue) + sizeof (ShadowValue) * (value->n_shadows - 1), value); } static GtkCssValue * gtk_css_value_shadow_compute (GtkCssValue *value, guint property_id, GtkStyleProvider *provider, GtkCssStyle *style, GtkCssStyle *parent_style) { guint i; ShadowValue *shadows; shadows = g_alloca (sizeof (ShadowValue) * value->n_shadows); for (i = 0; i < value->n_shadows; i++) { const ShadowValue *shadow = &value->shadows[i]; shadows[i].hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, provider, style, parent_style); shadows[i].voffset = _gtk_css_value_compute (shadow->voffset, property_id, provider, style, parent_style); shadows[i].radius = _gtk_css_value_compute (shadow->radius, property_id, provider, style, parent_style); shadows[i].spread = _gtk_css_value_compute (shadow->spread, property_id, provider, style, parent_style), shadows[i].color = _gtk_css_value_compute (shadow->color, property_id, provider, style, parent_style); shadows[i].inset = shadow->inset; } return gtk_css_shadow_value_new (shadows, value->n_shadows, value->is_filter); } static gboolean gtk_css_value_shadow_equal (const GtkCssValue *value1, const GtkCssValue *value2) { guint i; if (value1->n_shadows != value2->n_shadows) return FALSE; for (i = 0; i < value1->n_shadows; i++) { const ShadowValue *shadow1 = &value1->shadows[i]; const ShadowValue *shadow2 = &value2->shadows[i]; if (shadow1->inset != shadow2->inset || !_gtk_css_value_equal (shadow1->hoffset, shadow2->hoffset) || !_gtk_css_value_equal (shadow1->voffset, shadow2->voffset) || !_gtk_css_value_equal (shadow1->radius, shadow2->radius) || !_gtk_css_value_equal (shadow1->spread, shadow2->spread) || !_gtk_css_value_equal (shadow1->color, shadow2->color)) return FALSE; } return TRUE; } static GtkCssValue * gtk_css_value_shadow_transition (GtkCssValue *start, GtkCssValue *end, guint property_id, double progress) { guint i, len; ShadowValue *shadows; if (start->n_shadows > end->n_shadows) len = start->n_shadows; else len = end->n_shadows; shadows = g_newa (ShadowValue, len); for (i = 0; i < MIN (start->n_shadows, end->n_shadows); i++) { if (!shadow_value_transition (&start->shadows[i], &end->shadows[i], property_id, progress, &shadows[i])) { while (i--) shadow_value_unref (&shadows[i]); return NULL; } } if (start->n_shadows > end->n_shadows) { for (; i < len; i++) { ShadowValue fill; shadow_value_for_transition (&fill, start->shadows[i].inset); if (!shadow_value_transition (&start->shadows[i], &fill, property_id, progress, &shadows[i])) { while (i--) shadow_value_unref (&shadows[i]); shadow_value_unref (&fill); return NULL; } shadow_value_unref (&fill); } } else { for (; i < len; i++) { ShadowValue fill; shadow_value_for_transition (&fill, end->shadows[i].inset); if (!shadow_value_transition (&fill, &end->shadows[i], property_id, progress, &shadows[i])) { while (i--) shadow_value_unref (&shadows[i]); shadow_value_unref (&fill); return NULL; } shadow_value_unref (&fill); } } return gtk_css_shadow_value_new (shadows, len, start->is_filter); } static void gtk_css_value_shadow_print (const GtkCssValue *value, GString *string) { guint i; if (value->n_shadows == 0) { g_string_append (string, "none"); return; } for (i = 0; i < value->n_shadows; i ++) { const ShadowValue *shadow = &value->shadows[i]; if (i > 0) g_string_append (string, ", "); _gtk_css_value_print (shadow->hoffset, string); g_string_append_c (string, ' '); _gtk_css_value_print (shadow->voffset, string); g_string_append_c (string, ' '); if (_gtk_css_number_value_get (shadow->radius, 100) != 0 || _gtk_css_number_value_get (shadow->spread, 100) != 0) { _gtk_css_value_print (shadow->radius, string); g_string_append_c (string, ' '); } if (_gtk_css_number_value_get (shadow->spread, 100) != 0) { _gtk_css_value_print (shadow->spread, string); g_string_append_c (string, ' '); } _gtk_css_value_print (shadow->color, string); if (shadow->inset) g_string_append (string, " inset"); } } static const GtkCssValueClass GTK_CSS_VALUE_SHADOW = { "GtkCssShadowValue", gtk_css_value_shadow_free, gtk_css_value_shadow_compute, gtk_css_value_shadow_equal, gtk_css_value_shadow_transition, NULL, NULL, gtk_css_value_shadow_print }; static GtkCssValue shadow_none_singleton = { >K_CSS_VALUE_SHADOW, 1, TRUE, FALSE, 0 }; GtkCssValue * gtk_css_shadow_value_new_none (void) { return _gtk_css_value_ref (&shadow_none_singleton); } static GtkCssValue * gtk_css_shadow_value_new (ShadowValue *shadows, guint n_shadows, gboolean is_filter) { GtkCssValue *retval; guint i; if (n_shadows == 0) return gtk_css_shadow_value_new_none (); retval = _gtk_css_value_alloc (>K_CSS_VALUE_SHADOW, sizeof (GtkCssValue) + sizeof (ShadowValue) * (n_shadows - 1)); retval->n_shadows = n_shadows; retval->is_filter = is_filter; memcpy (retval->shadows, shadows, sizeof (ShadowValue) * n_shadows); retval->is_computed = TRUE; for (i = 0; i < n_shadows; i++) { const ShadowValue *shadow = &retval->shadows[i]; if (!gtk_css_value_is_computed (shadow->hoffset) || !gtk_css_value_is_computed (shadow->voffset) || !gtk_css_value_is_computed (shadow->spread) || !gtk_css_value_is_computed (shadow->radius) || !gtk_css_value_is_computed (shadow->color)) { retval->is_computed = FALSE; break; } } return retval; } GtkCssValue * gtk_css_shadow_value_new_filter (void) { ShadowValue value; value.inset = FALSE; value.hoffset = _gtk_css_number_value_new (0, GTK_CSS_NUMBER); value.voffset = _gtk_css_number_value_new (0, GTK_CSS_NUMBER); value.radius = _gtk_css_number_value_new (0, GTK_CSS_NUMBER); value.spread = _gtk_css_number_value_new (0, GTK_CSS_NUMBER); value.color = _gtk_css_color_value_new_current_color (); return gtk_css_shadow_value_new (&value, 1, TRUE); } enum { HOFFSET, VOFFSET, RADIUS, SPREAD, N_VALUES }; static gboolean has_inset (GtkCssParser *parser, gpointer option_data, gpointer box_shadow_mode) { return box_shadow_mode && gtk_css_parser_has_ident (parser, "inset"); } static gboolean parse_inset (GtkCssParser *parser, gpointer option_data, gpointer box_shadow_mode) { gboolean *inset = option_data; if (!gtk_css_parser_try_ident (parser, "inset")) { g_assert_not_reached (); return FALSE; } *inset = TRUE; return TRUE; } static gboolean parse_lengths (GtkCssParser *parser, gpointer option_data, gpointer box_shadow_mode) { GtkCssValue **values = option_data; values[HOFFSET] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH); if (values[HOFFSET] == NULL) return FALSE; values[VOFFSET] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH); if (values[VOFFSET] == NULL) return FALSE; if (gtk_css_number_value_can_parse (parser)) { values[RADIUS] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH | GTK_CSS_POSITIVE_ONLY); if (values[RADIUS] == NULL) return FALSE; } else values[RADIUS] = _gtk_css_number_value_new (0.0, GTK_CSS_PX); if (box_shadow_mode && gtk_css_number_value_can_parse (parser)) { values[SPREAD] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH); if (values[SPREAD] == NULL) return FALSE; } else values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX); return TRUE; } static gboolean parse_color (GtkCssParser *parser, gpointer option_data, gpointer box_shadow_mode) { GtkCssValue **color = option_data; *color = _gtk_css_color_value_parse (parser); if (*color == NULL) return FALSE; return TRUE; } static gboolean gtk_css_shadow_value_parse_one (GtkCssParser *parser, gboolean box_shadow_mode, ShadowValue *result) { GtkCssValue *values[N_VALUES] = { NULL, }; GtkCssValue *color = NULL; gboolean inset = FALSE; GtkCssParseOption options[] = { { (void *) gtk_css_number_value_can_parse, parse_lengths, values }, { has_inset, parse_inset, &inset }, { (void *) gtk_css_color_value_can_parse, parse_color, &color }, }; guint i; if (!gtk_css_parser_consume_any (parser, options, G_N_ELEMENTS (options), GUINT_TO_POINTER (box_shadow_mode))) goto fail; if (values[0] == NULL) { gtk_css_parser_error_syntax (parser, "Expected shadow value to contain a length"); goto fail; } if (color == NULL) color = _gtk_css_color_value_new_current_color (); result->hoffset = values[HOFFSET]; result->voffset = values[VOFFSET]; result->spread = values[SPREAD]; result->radius = values[RADIUS]; result->color = color; result->inset = inset; return TRUE; fail: for (i = 0; i < N_VALUES; i++) { g_clear_pointer (&values[i], gtk_css_value_unref); } g_clear_pointer (&color, gtk_css_value_unref); return FALSE; } #define MAX_SHADOWS 64 GtkCssValue * gtk_css_shadow_value_parse (GtkCssParser *parser, gboolean box_shadow_mode) { ShadowValue shadows[MAX_SHADOWS]; int n_shadows = 0; int i; if (gtk_css_parser_try_ident (parser, "none")) return gtk_css_shadow_value_new_none (); do { if (n_shadows == MAX_SHADOWS) { gtk_css_parser_error_syntax (parser, "Not more than %d shadows supported", MAX_SHADOWS); goto fail; } if (gtk_css_shadow_value_parse_one (parser, box_shadow_mode, &shadows[n_shadows])) n_shadows++; } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA)); return gtk_css_shadow_value_new (shadows, n_shadows, FALSE); fail: for (i = 0; i < n_shadows; i++) { const ShadowValue *shadow = &shadows[i]; shadow_value_unref (shadow); } return NULL; } GtkCssValue * gtk_css_shadow_value_parse_filter (GtkCssParser *parser) { ShadowValue shadow; if (gtk_css_shadow_value_parse_one (parser, FALSE, &shadow)) return gtk_css_shadow_value_new (&shadow, 1, TRUE); else return NULL; } void gtk_css_shadow_value_get_extents (const GtkCssValue *value, GtkBorder *border) { guint i; memset (border, 0, sizeof (GtkBorder)); for (i = 0; i < value->n_shadows; i ++) { const ShadowValue *shadow = &value->shadows[i]; double hoffset, voffset, spread, radius, clip_radius; spread = _gtk_css_number_value_get (shadow->spread, 0); radius = _gtk_css_number_value_get (shadow->radius, 0); if (!value->is_filter) radius = radius / 2.0; clip_radius = gsk_cairo_blur_compute_pixels (radius); hoffset = _gtk_css_number_value_get (shadow->hoffset, 0); voffset = _gtk_css_number_value_get (shadow->voffset, 0); border->top = MAX (border->top, ceil (clip_radius + spread - voffset)); border->right = MAX (border->right, ceil (clip_radius + spread + hoffset)); border->bottom = MAX (border->bottom, ceil (clip_radius + spread + voffset)); border->left = MAX (border->left, ceil (clip_radius + spread - hoffset)); } } void gtk_css_shadow_value_snapshot_outset (const GtkCssValue *value, GtkSnapshot *snapshot, const GskRoundedRect *border_box) { guint i; double dx, dy, spread, radius; const GdkRGBA *color; g_return_if_fail (value->class == >K_CSS_VALUE_SHADOW); for (i = 0; i < value->n_shadows; i ++) { const ShadowValue *shadow = &value->shadows[i]; if (shadow->inset) continue; color = gtk_css_color_value_get_rgba (shadow->color); /* We don't need to draw invisible shadows */ if (gdk_rgba_is_clear (color)) continue; dx = _gtk_css_number_value_get (shadow->hoffset, 0); dy = _gtk_css_number_value_get (shadow->voffset, 0); spread = _gtk_css_number_value_get (shadow->spread, 0); radius = _gtk_css_number_value_get (shadow->radius, 0); if (value->is_filter) radius = 2 * radius; gtk_snapshot_append_outset_shadow (snapshot, border_box, color, dx, dy, spread, radius); } } void gtk_css_shadow_value_snapshot_inset (const GtkCssValue *value, GtkSnapshot *snapshot, const GskRoundedRect *padding_box) { guint i; double dx, dy, spread, radius; const GdkRGBA *color; g_return_if_fail (value->class == >K_CSS_VALUE_SHADOW); for (i = 0; i < value->n_shadows; i ++) { const ShadowValue *shadow = &value->shadows[i]; if (!shadow->inset) continue; color = gtk_css_color_value_get_rgba (shadow->color); /* We don't need to draw invisible shadows */ if (gdk_rgba_is_clear (color)) continue; dx = _gtk_css_number_value_get (shadow->hoffset, 0); dy = _gtk_css_number_value_get (shadow->voffset, 0); spread = _gtk_css_number_value_get (shadow->spread, 0); radius = _gtk_css_number_value_get (shadow->radius, 0); if (value->is_filter) radius = 2 * radius; /* These are trivial to do with a color node */ if (spread == 0 && radius == 0 && gsk_rounded_rect_is_rectilinear (padding_box)) { const graphene_rect_t *padding_bounds = &padding_box->bounds; if (dx > 0) { const float y = dy > 0 ? dy : 0; gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT ( padding_bounds->origin.x, padding_bounds->origin.y + y, dx, padding_bounds->size.height - ABS (dy) )); } else if (dx < 0) { const float y = dy > 0 ? dy : 0; gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT ( padding_bounds->origin.x + padding_bounds->size.width + dx, padding_bounds->origin.y + y, - dx, padding_bounds->size.height - ABS (dy) )); } if (dy > 0) { gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT ( padding_bounds->origin.x, padding_bounds->origin.y, padding_bounds->size.width, dy )); } else if (dy < 0) { gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT ( padding_bounds->origin.x, padding_bounds->origin.y + padding_bounds->size.height + dy, padding_bounds->size.width, - dy )); } } else { gtk_snapshot_append_inset_shadow (snapshot, padding_box, color, dx, dy, spread, radius); } } } gboolean gtk_css_shadow_value_is_clear (const GtkCssValue *value) { guint i; if (!value) return TRUE; for (i = 0; i < value->n_shadows; i++) { const ShadowValue *shadow = &value->shadows[i]; if (!gdk_rgba_is_clear (gtk_css_color_value_get_rgba (shadow->color))) return FALSE; } return TRUE; } gboolean gtk_css_shadow_value_is_none (const GtkCssValue *shadow) { return !shadow || shadow->n_shadows == 0; } gboolean gtk_css_shadow_value_push_snapshot (const GtkCssValue *value, GtkSnapshot *snapshot) { gboolean need_shadow = FALSE; guint i; for (i = 0; i < value->n_shadows; i++) { const ShadowValue *shadow = &value->shadows[i]; if (!gdk_rgba_is_clear (gtk_css_color_value_get_rgba (shadow->color))) { need_shadow = TRUE; break; } } if (need_shadow) { GskShadow *shadows = g_newa (GskShadow, value->n_shadows); for (i = 0; i < value->n_shadows; i++) { const ShadowValue *shadow = &value->shadows[i]; shadows[i].dx = _gtk_css_number_value_get (shadow->hoffset, 0); shadows[i].dy = _gtk_css_number_value_get (shadow->voffset, 0); shadows[i].color = *gtk_css_color_value_get_rgba (shadow->color); shadows[i].radius = _gtk_css_number_value_get (shadow->radius, 0); if (value->is_filter) shadows[i].radius *= 2; } gtk_snapshot_push_shadow (snapshot, shadows, value->n_shadows); } return need_shadow; } void gtk_css_shadow_value_pop_snapshot (const GtkCssValue *value, GtkSnapshot *snapshot) { gboolean need_shadow = FALSE; guint i; for (i = 0; i < value->n_shadows; i++) { const ShadowValue *shadow = &value->shadows[i]; if (!gdk_rgba_is_clear (gtk_css_color_value_get_rgba (shadow->color))) { need_shadow = TRUE; break; } } if (need_shadow) gtk_snapshot_pop (snapshot); }