forked from AuroraMiddleware/gtk
f478f16dba
Adds conditional code paths to GdkCssShadowValue for painting outset shadows, and allows shadows to be applied in two passes (first outset then inset). This can be used to draw csd shadows in outer window borders. https://bugzilla.gnome.org/show_bug.cgi?id=695998 Signed-off-by: Rob Bradford <rob@linux.intel.com>
502 lines
16 KiB
C
502 lines
16 KiB
C
/* GTK - The GIMP Toolkit
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
*
|
|
* Author: Cosimo Cecchi <cosimoc@gnome.org>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkcssshadowvalueprivate.h"
|
|
|
|
#include "gtkcairoblurprivate.h"
|
|
#include "gtkcsscolorvalueprivate.h"
|
|
#include "gtkcssnumbervalueprivate.h"
|
|
#include "gtkcssrgbavalueprivate.h"
|
|
#include "gtkstylecontextprivate.h"
|
|
#include "gtkthemingengineprivate.h"
|
|
#include "gtkpango.h"
|
|
|
|
struct _GtkCssValue {
|
|
GTK_CSS_VALUE_BASE
|
|
guint inset :1;
|
|
|
|
GtkCssValue *hoffset;
|
|
GtkCssValue *voffset;
|
|
GtkCssValue *radius;
|
|
GtkCssValue *spread;
|
|
|
|
GtkCssValue *color;
|
|
};
|
|
|
|
static GtkCssValue * gtk_css_shadow_value_new (GtkCssValue *hoffset,
|
|
GtkCssValue *voffset,
|
|
GtkCssValue *radius,
|
|
GtkCssValue *spread,
|
|
gboolean inset,
|
|
GtkCssValue *color);
|
|
|
|
static void
|
|
gtk_css_value_shadow_free (GtkCssValue *shadow)
|
|
{
|
|
_gtk_css_value_unref (shadow->hoffset);
|
|
_gtk_css_value_unref (shadow->voffset);
|
|
_gtk_css_value_unref (shadow->radius);
|
|
_gtk_css_value_unref (shadow->spread);
|
|
_gtk_css_value_unref (shadow->color);
|
|
|
|
g_slice_free (GtkCssValue, shadow);
|
|
}
|
|
|
|
static GtkCssValue *
|
|
gtk_css_value_shadow_compute (GtkCssValue *shadow,
|
|
guint property_id,
|
|
GtkStyleProviderPrivate *provider,
|
|
GtkCssComputedValues *values,
|
|
GtkCssComputedValues *parent_values,
|
|
GtkCssDependencies *dependencies)
|
|
{
|
|
GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
|
|
GtkCssDependencies child_deps;
|
|
|
|
child_deps = 0;
|
|
hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, provider, values, parent_values, &child_deps);
|
|
*dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
|
|
|
|
child_deps = 0;
|
|
voffset = _gtk_css_value_compute (shadow->voffset, property_id, provider, values, parent_values, &child_deps);
|
|
*dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
|
|
|
|
child_deps = 0;
|
|
radius = _gtk_css_value_compute (shadow->radius, property_id, provider, values, parent_values, &child_deps);
|
|
*dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
|
|
|
|
child_deps = 0;
|
|
spread = _gtk_css_value_compute (shadow->spread, property_id, provider, values, parent_values, &child_deps),
|
|
*dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
|
|
|
|
child_deps = 0;
|
|
color = _gtk_css_value_compute (shadow->color, property_id, provider, values, parent_values, &child_deps);
|
|
*dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
|
|
|
|
return gtk_css_shadow_value_new (hoffset, voffset, radius, spread, shadow->inset, color);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_css_value_shadow_equal (const GtkCssValue *shadow1,
|
|
const GtkCssValue *shadow2)
|
|
{
|
|
return 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);
|
|
}
|
|
|
|
static GtkCssValue *
|
|
gtk_css_value_shadow_transition (GtkCssValue *start,
|
|
GtkCssValue *end,
|
|
guint property_id,
|
|
double progress)
|
|
{
|
|
if (start->inset != end->inset)
|
|
return NULL;
|
|
|
|
return gtk_css_shadow_value_new (_gtk_css_value_transition (start->hoffset, end->hoffset, property_id, progress),
|
|
_gtk_css_value_transition (start->voffset, end->voffset, property_id, progress),
|
|
_gtk_css_value_transition (start->radius, end->radius, property_id, progress),
|
|
_gtk_css_value_transition (start->spread, end->spread, property_id, progress),
|
|
start->inset,
|
|
_gtk_css_value_transition (start->color, end->color, property_id, progress));
|
|
}
|
|
|
|
static void
|
|
gtk_css_value_shadow_print (const GtkCssValue *shadow,
|
|
GString *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 = {
|
|
gtk_css_value_shadow_free,
|
|
gtk_css_value_shadow_compute,
|
|
gtk_css_value_shadow_equal,
|
|
gtk_css_value_shadow_transition,
|
|
gtk_css_value_shadow_print
|
|
};
|
|
|
|
static GtkCssValue *
|
|
gtk_css_shadow_value_new (GtkCssValue *hoffset,
|
|
GtkCssValue *voffset,
|
|
GtkCssValue *radius,
|
|
GtkCssValue *spread,
|
|
gboolean inset,
|
|
GtkCssValue *color)
|
|
{
|
|
GtkCssValue *retval;
|
|
|
|
retval = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_SHADOW);
|
|
|
|
retval->hoffset = hoffset;
|
|
retval->voffset = voffset;
|
|
retval->radius = radius;
|
|
retval->spread = spread;
|
|
retval->inset = inset;
|
|
retval->color = color;
|
|
|
|
return retval;
|
|
}
|
|
|
|
GtkCssValue *
|
|
_gtk_css_shadow_value_new_for_transition (GtkCssValue *target)
|
|
{
|
|
GdkRGBA transparent = { 0, 0, 0, 0 };
|
|
|
|
g_return_val_if_fail (target->class == >K_CSS_VALUE_SHADOW, NULL);
|
|
|
|
return gtk_css_shadow_value_new (_gtk_css_number_value_new (0, GTK_CSS_PX),
|
|
_gtk_css_number_value_new (0, GTK_CSS_PX),
|
|
_gtk_css_number_value_new (0, GTK_CSS_PX),
|
|
_gtk_css_number_value_new (0, GTK_CSS_PX),
|
|
target->inset,
|
|
_gtk_css_rgba_value_new_from_rgba (&transparent));
|
|
}
|
|
|
|
static gboolean
|
|
value_is_done_parsing (GtkCssParser *parser)
|
|
{
|
|
return _gtk_css_parser_is_eof (parser) ||
|
|
_gtk_css_parser_begins_with (parser, ',') ||
|
|
_gtk_css_parser_begins_with (parser, ';') ||
|
|
_gtk_css_parser_begins_with (parser, '}');
|
|
}
|
|
|
|
GtkCssValue *
|
|
_gtk_css_shadow_value_parse (GtkCssParser *parser)
|
|
{
|
|
enum {
|
|
HOFFSET,
|
|
VOFFSET,
|
|
RADIUS,
|
|
SPREAD,
|
|
COLOR,
|
|
N_VALUES
|
|
};
|
|
GtkCssValue *values[N_VALUES] = { NULL, };
|
|
gboolean inset;
|
|
guint i;
|
|
|
|
inset = _gtk_css_parser_try (parser, "inset", TRUE);
|
|
|
|
do
|
|
{
|
|
if (values[HOFFSET] == NULL &&
|
|
_gtk_css_parser_has_number (parser))
|
|
{
|
|
values[HOFFSET] = _gtk_css_number_value_parse (parser,
|
|
GTK_CSS_PARSE_LENGTH
|
|
| GTK_CSS_NUMBER_AS_PIXELS);
|
|
if (values[HOFFSET] == NULL)
|
|
goto fail;
|
|
|
|
values[VOFFSET] = _gtk_css_number_value_parse (parser,
|
|
GTK_CSS_PARSE_LENGTH
|
|
| GTK_CSS_NUMBER_AS_PIXELS);
|
|
if (values[VOFFSET] == NULL)
|
|
goto fail;
|
|
|
|
if (_gtk_css_parser_has_number (parser))
|
|
{
|
|
values[RADIUS] = _gtk_css_number_value_parse (parser,
|
|
GTK_CSS_PARSE_LENGTH
|
|
| GTK_CSS_POSITIVE_ONLY
|
|
| GTK_CSS_NUMBER_AS_PIXELS);
|
|
if (values[RADIUS] == NULL)
|
|
goto fail;
|
|
}
|
|
else
|
|
values[RADIUS] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
|
|
|
|
if (_gtk_css_parser_has_number (parser))
|
|
{
|
|
values[SPREAD] = _gtk_css_number_value_parse (parser,
|
|
GTK_CSS_PARSE_LENGTH
|
|
| GTK_CSS_NUMBER_AS_PIXELS);
|
|
if (values[SPREAD] == NULL)
|
|
goto fail;
|
|
}
|
|
else
|
|
values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
|
|
}
|
|
else if (!inset && _gtk_css_parser_try (parser, "inset", TRUE))
|
|
{
|
|
if (values[HOFFSET] == NULL)
|
|
goto fail;
|
|
inset = TRUE;
|
|
break;
|
|
}
|
|
else if (values[COLOR] == NULL)
|
|
{
|
|
values[COLOR] = _gtk_css_color_value_parse (parser);
|
|
|
|
if (values[COLOR] == NULL)
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
/* We parsed everything and there's still stuff left?
|
|
* Pretend we didn't notice and let the normal code produce
|
|
* a 'junk at end of value' error */
|
|
goto fail;
|
|
}
|
|
}
|
|
while (values[HOFFSET] == NULL || !value_is_done_parsing (parser));
|
|
|
|
if (values[COLOR] == NULL)
|
|
values[COLOR] = _gtk_css_color_value_new_current_color ();
|
|
|
|
return gtk_css_shadow_value_new (values[HOFFSET], values[VOFFSET],
|
|
values[RADIUS], values[SPREAD],
|
|
inset, values[COLOR]);
|
|
|
|
fail:
|
|
for (i = 0; i < N_VALUES; i++)
|
|
{
|
|
if (values[i])
|
|
_gtk_css_value_unref (values[i]);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const cairo_user_data_key_t shadow_key;
|
|
|
|
static cairo_t *
|
|
gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
|
|
cairo_t *cr)
|
|
{
|
|
cairo_rectangle_int_t clip_rect;
|
|
cairo_surface_t *surface;
|
|
cairo_t *blur_cr;
|
|
gdouble radius;
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
if (radius == 0.0)
|
|
return cr;
|
|
|
|
gdk_cairo_get_clip_rectangle (cr, &clip_rect);
|
|
|
|
/* Create a larger surface to center the blur. */
|
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
clip_rect.width + 2 * radius,
|
|
clip_rect.height + 2 * radius);
|
|
cairo_surface_set_device_offset (surface, radius - clip_rect.x, radius - clip_rect.y);
|
|
blur_cr = cairo_create (surface);
|
|
cairo_set_user_data (blur_cr, &shadow_key, cairo_reference (cr), (cairo_destroy_func_t) cairo_destroy);
|
|
|
|
if (cairo_has_current_point (cr))
|
|
{
|
|
double x, y;
|
|
|
|
cairo_get_current_point (cr, &x, &y);
|
|
cairo_move_to (blur_cr, x, y);
|
|
}
|
|
|
|
return blur_cr;
|
|
}
|
|
|
|
static cairo_t *
|
|
gtk_css_shadow_value_finish_drawing (const GtkCssValue *shadow,
|
|
cairo_t *cr)
|
|
{
|
|
gdouble radius;
|
|
cairo_t *original_cr;
|
|
cairo_surface_t *surface;
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
if (radius == 0.0)
|
|
return cr;
|
|
|
|
surface = cairo_get_target (cr);
|
|
original_cr = cairo_get_user_data (cr, &shadow_key);
|
|
|
|
/* Blur the surface. */
|
|
_gtk_cairo_blur_surface (surface, radius);
|
|
|
|
cairo_set_source_surface (original_cr, surface, 0, 0);
|
|
cairo_paint (original_cr);
|
|
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (surface);
|
|
|
|
return original_cr;
|
|
}
|
|
|
|
void
|
|
_gtk_css_shadow_value_paint_layout (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
PangoLayout *layout)
|
|
{
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
if (!cairo_has_current_point (cr))
|
|
cairo_move_to (cr, 0, 0);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_rel_move_to (cr,
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0));
|
|
|
|
cr = gtk_css_shadow_value_start_drawing (shadow, cr);
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
_gtk_pango_fill_layout (cr, layout);
|
|
|
|
cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
|
|
|
|
cairo_rel_move_to (cr,
|
|
- _gtk_css_number_value_get (shadow->hoffset, 0),
|
|
- _gtk_css_number_value_get (shadow->voffset, 0));
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
void
|
|
_gtk_css_shadow_value_paint_icon (const GtkCssValue *shadow,
|
|
cairo_t *cr)
|
|
{
|
|
cairo_pattern_t *pattern;
|
|
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
cairo_save (cr);
|
|
pattern = cairo_pattern_reference (cairo_get_source (cr));
|
|
|
|
cr = gtk_css_shadow_value_start_drawing (shadow, cr);
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
|
|
cairo_translate (cr,
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0));
|
|
cairo_mask (cr, pattern);
|
|
|
|
cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
|
|
|
|
cairo_restore (cr);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
void
|
|
_gtk_css_shadow_value_paint_spinner (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
gdouble radius,
|
|
gdouble progress)
|
|
{
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
cairo_save (cr);
|
|
|
|
cr = gtk_css_shadow_value_start_drawing (shadow, cr);
|
|
|
|
cairo_translate (cr,
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0));
|
|
_gtk_theming_engine_paint_spinner (cr,
|
|
radius, progress,
|
|
_gtk_css_rgba_value_get_rgba (shadow->color));
|
|
|
|
cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
gboolean
|
|
_gtk_css_shadow_value_get_inset (const GtkCssValue *shadow)
|
|
{
|
|
g_return_val_if_fail (shadow->class == >K_CSS_VALUE_SHADOW, FALSE);
|
|
|
|
return shadow->inset;
|
|
}
|
|
|
|
void
|
|
_gtk_css_shadow_value_paint_box (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
const GtkRoundedBox *padding_box)
|
|
{
|
|
GtkRoundedBox box, clip_box;
|
|
double spread, radius;
|
|
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
cairo_save (cr);
|
|
|
|
if (shadow->inset)
|
|
{
|
|
_gtk_rounded_box_path (padding_box, cr);
|
|
cairo_clip (cr);
|
|
}
|
|
|
|
box = *padding_box;
|
|
_gtk_rounded_box_move (&box,
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0));
|
|
spread = _gtk_css_number_value_get (shadow->spread, 0);
|
|
if (shadow->inset)
|
|
_gtk_rounded_box_shrink (&box, spread, spread, spread, spread);
|
|
else /* Outset */
|
|
_gtk_rounded_box_grow (&box, spread, spread, spread, spread);
|
|
|
|
clip_box = *padding_box;
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
_gtk_rounded_box_shrink (&clip_box, -radius, -radius, -radius, -radius);
|
|
|
|
cr = gtk_css_shadow_value_start_drawing (shadow, cr);
|
|
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
_gtk_rounded_box_path (&box, cr);
|
|
if (shadow->inset)
|
|
_gtk_rounded_box_clip_path (&clip_box, cr);
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
cairo_fill (cr);
|
|
|
|
cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|