forked from AuroraMiddleware/gtk
73b4a62f51
Instead of every snapshot function having debug messages, have an explicit gtk_snapshot_push_debug() function that appends a debug node.
1097 lines
36 KiB
C
1097 lines
36 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 "gtkcsscolorvalueprivate.h"
|
|
#include "gtkcssnumbervalueprivate.h"
|
|
#include "gtkcssrgbavalueprivate.h"
|
|
#include "gtksnapshotprivate.h"
|
|
#include "gtkstylecontextprivate.h"
|
|
#include "gtkpango.h"
|
|
|
|
#include "gsk/gskcairoblurprivate.h"
|
|
#include "gsk/gskroundedrectprivate.h"
|
|
|
|
#include <math.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,
|
|
GtkStyleProvider *provider,
|
|
GtkCssStyle *style,
|
|
GtkCssStyle *parent_style)
|
|
{
|
|
GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
|
|
|
|
hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, provider, style, parent_style);
|
|
voffset = _gtk_css_value_compute (shadow->voffset, property_id, provider, style, parent_style);
|
|
radius = _gtk_css_value_compute (shadow->radius, property_id, provider, style, parent_style);
|
|
spread = _gtk_css_value_compute (shadow->spread, property_id, provider, style, parent_style),
|
|
color = _gtk_css_value_compute (shadow->color, property_id, provider, style, parent_style);
|
|
|
|
if (hoffset == shadow->hoffset &&
|
|
voffset == shadow->voffset &&
|
|
radius == shadow->radius &&
|
|
spread == shadow->spread &&
|
|
color == shadow->color)
|
|
{
|
|
_gtk_css_value_unref (hoffset);
|
|
_gtk_css_value_unref (voffset);
|
|
_gtk_css_value_unref (radius);
|
|
_gtk_css_value_unref (spread);
|
|
_gtk_css_value_unref (color);
|
|
|
|
return _gtk_css_value_ref (shadow);
|
|
}
|
|
|
|
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,
|
|
NULL,
|
|
NULL,
|
|
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,
|
|
gboolean box_shadow_mode)
|
|
{
|
|
enum {
|
|
HOFFSET,
|
|
VOFFSET,
|
|
RADIUS,
|
|
SPREAD,
|
|
COLOR,
|
|
N_VALUES
|
|
};
|
|
GtkCssValue *values[N_VALUES] = { NULL, };
|
|
gboolean inset;
|
|
guint i;
|
|
|
|
if (box_shadow_mode)
|
|
inset = _gtk_css_parser_try (parser, "inset", TRUE);
|
|
else
|
|
inset = FALSE;
|
|
|
|
do
|
|
{
|
|
if (values[HOFFSET] == NULL &&
|
|
gtk_css_number_value_can_parse (parser))
|
|
{
|
|
values[HOFFSET] = _gtk_css_number_value_parse (parser,
|
|
GTK_CSS_PARSE_LENGTH);
|
|
if (values[HOFFSET] == NULL)
|
|
goto fail;
|
|
|
|
values[VOFFSET] = _gtk_css_number_value_parse (parser,
|
|
GTK_CSS_PARSE_LENGTH);
|
|
if (values[VOFFSET] == NULL)
|
|
goto fail;
|
|
|
|
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)
|
|
goto fail;
|
|
}
|
|
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)
|
|
goto fail;
|
|
}
|
|
else
|
|
values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
|
|
}
|
|
else if (!inset && box_shadow_mode && _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 gboolean
|
|
needs_blur (const GtkCssValue *shadow)
|
|
{
|
|
double radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
|
|
/* The code doesn't actually do any blurring for radius 1, as it
|
|
* ends up with box filter size 1 */
|
|
if (radius <= 1.0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static const cairo_user_data_key_t original_cr_key;
|
|
|
|
static cairo_t *
|
|
gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
GskBlurFlags blur_flags)
|
|
{
|
|
cairo_rectangle_int_t clip_rect;
|
|
cairo_surface_t *surface;
|
|
cairo_t *blur_cr;
|
|
gdouble radius, clip_radius;
|
|
gdouble x_scale, y_scale;
|
|
gboolean blur_x = (blur_flags & GSK_BLUR_X) != 0;
|
|
gboolean blur_y = (blur_flags & GSK_BLUR_Y) != 0;
|
|
|
|
if (!needs_blur (shadow))
|
|
return cr;
|
|
|
|
gdk_cairo_get_clip_rectangle (cr, &clip_rect);
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
x_scale = y_scale = 1;
|
|
cairo_surface_get_device_scale (cairo_get_target (cr), &x_scale, &y_scale);
|
|
|
|
if (blur_flags & GSK_BLUR_REPEAT)
|
|
{
|
|
if (!blur_x)
|
|
clip_rect.width = 1;
|
|
if (!blur_y)
|
|
clip_rect.height = 1;
|
|
}
|
|
|
|
/* Create a larger surface to center the blur. */
|
|
surface = cairo_surface_create_similar_image (cairo_get_target (cr),
|
|
CAIRO_FORMAT_A8,
|
|
x_scale * (clip_rect.width + (blur_x ? 2 * clip_radius : 0)),
|
|
y_scale * (clip_rect.height + (blur_y ? 2 * clip_radius : 0)));
|
|
cairo_surface_set_device_scale (surface, x_scale, y_scale);
|
|
cairo_surface_set_device_offset (surface,
|
|
x_scale * ((blur_x ? clip_radius: 0) - clip_rect.x),
|
|
y_scale * ((blur_y ? clip_radius: 0) - clip_rect.y));
|
|
|
|
blur_cr = cairo_create (surface);
|
|
cairo_set_user_data (blur_cr, &original_cr_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 void
|
|
mask_surface_repeat (cairo_t *cr,
|
|
cairo_surface_t *surface)
|
|
{
|
|
cairo_pattern_t *pattern;
|
|
|
|
pattern = cairo_pattern_create_for_surface (surface);
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
|
|
cairo_mask (cr, pattern);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static cairo_t *
|
|
gtk_css_shadow_value_finish_drawing (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
GskBlurFlags blur_flags)
|
|
{
|
|
gdouble radius;
|
|
cairo_t *original_cr;
|
|
cairo_surface_t *surface;
|
|
gdouble x_scale;
|
|
|
|
if (!needs_blur (shadow))
|
|
return cr;
|
|
|
|
original_cr = cairo_get_user_data (cr, &original_cr_key);
|
|
|
|
/* Blur the surface. */
|
|
surface = cairo_get_target (cr);
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
|
|
x_scale = 1;
|
|
cairo_surface_get_device_scale (cairo_get_target (cr), &x_scale, NULL);
|
|
|
|
gsk_cairo_blur_surface (surface, x_scale * radius, blur_flags);
|
|
|
|
gdk_cairo_set_source_rgba (original_cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
if (blur_flags & GSK_BLUR_REPEAT)
|
|
mask_surface_repeat (original_cr, surface);
|
|
else
|
|
cairo_mask_surface (original_cr, surface, 0, 0);
|
|
|
|
cairo_destroy (cr);
|
|
|
|
cairo_surface_destroy (surface);
|
|
|
|
return original_cr;
|
|
}
|
|
|
|
static const cairo_user_data_key_t radius_key;
|
|
static const cairo_user_data_key_t layout_serial_key;
|
|
|
|
GQuark pango_cached_blurred_surface_quark (void);
|
|
|
|
G_DEFINE_QUARK (GtkCssShadowValue pango_cached_blurred_surface, pango_cached_blurred_surface)
|
|
|
|
static cairo_surface_t *
|
|
get_cached_pango_surface (PangoLayout *layout,
|
|
const GtkCssValue *shadow)
|
|
{
|
|
cairo_surface_t *cached_surface = g_object_get_qdata (G_OBJECT (layout), pango_cached_blurred_surface_quark ());
|
|
guint cached_radius, cached_serial;
|
|
guint radius, serial;
|
|
|
|
if (!cached_surface)
|
|
return NULL;
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
cached_radius = GPOINTER_TO_UINT (cairo_surface_get_user_data (cached_surface, &radius_key));
|
|
if (radius != cached_radius)
|
|
return NULL;
|
|
|
|
serial = pango_layout_get_serial (layout);
|
|
cached_serial = GPOINTER_TO_UINT (cairo_surface_get_user_data (cached_surface, &layout_serial_key));
|
|
if (serial != cached_serial)
|
|
return NULL;
|
|
|
|
return cached_surface;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
make_blurred_pango_surface (cairo_t *existing_cr,
|
|
PangoLayout *layout,
|
|
const GtkCssValue *shadow)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
gdouble radius, clip_radius;
|
|
gdouble x_scale, y_scale;
|
|
PangoRectangle ink_rect;
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
|
|
pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
x_scale = y_scale = 1;
|
|
cairo_surface_get_device_scale (cairo_get_target (existing_cr), &x_scale, &y_scale);
|
|
|
|
surface = cairo_surface_create_similar_image (cairo_get_target (existing_cr),
|
|
CAIRO_FORMAT_A8,
|
|
x_scale * (ink_rect.width + 2 * clip_radius),
|
|
y_scale * (ink_rect.height + 2 * clip_radius));
|
|
cairo_surface_set_device_scale (surface, x_scale, y_scale);
|
|
cairo_surface_set_device_offset (surface, -ink_rect.x + clip_radius, -ink_rect.y + clip_radius);
|
|
cr = cairo_create (surface);
|
|
cairo_move_to (cr, 0, 0);
|
|
_gtk_pango_fill_layout (cr, layout);
|
|
gsk_cairo_blur_surface (surface, radius * x_scale, GSK_BLUR_X | GSK_BLUR_Y);
|
|
|
|
cairo_destroy (cr);
|
|
|
|
return surface;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
get_blurred_pango_surface (cairo_t *cr,
|
|
PangoLayout *layout,
|
|
const GtkCssValue *shadow)
|
|
{
|
|
cairo_surface_t *surface;
|
|
guint radius, serial;
|
|
|
|
surface = get_cached_pango_surface (layout, shadow);
|
|
if (!surface)
|
|
{
|
|
surface = make_blurred_pango_surface (cr, layout, shadow);
|
|
|
|
/* Cache the surface on the PangoLayout */
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
cairo_surface_set_user_data (surface, &radius_key, GUINT_TO_POINTER (radius), NULL);
|
|
|
|
serial = pango_layout_get_serial (layout);
|
|
cairo_surface_set_user_data (surface, &layout_serial_key, GUINT_TO_POINTER (serial), NULL);
|
|
|
|
g_object_set_qdata_full (G_OBJECT (layout), pango_cached_blurred_surface_quark (),
|
|
surface, (GDestroyNotify) cairo_surface_destroy);
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
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);
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
|
|
return;
|
|
|
|
if (!cairo_has_current_point (cr))
|
|
cairo_move_to (cr, 0, 0);
|
|
|
|
cairo_save (cr);
|
|
|
|
if (needs_blur (shadow))
|
|
{
|
|
cairo_surface_t *blurred_surface = get_blurred_pango_surface (cr, layout, shadow);
|
|
double x, y;
|
|
cairo_get_current_point (cr, &x, &y);
|
|
cairo_translate (cr, x, y);
|
|
cairo_translate (cr,
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0));
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
cairo_mask_surface (cr, blurred_surface, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
/* The no blur case -- just paint directly. */
|
|
cairo_rel_move_to (cr,
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0));
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
_gtk_pango_fill_layout (cr, layout);
|
|
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);
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
|
|
return;
|
|
|
|
cairo_save (cr);
|
|
pattern = cairo_pattern_reference (cairo_get_source (cr));
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
cr = gtk_css_shadow_value_start_drawing (shadow, cr, GSK_BLUR_X | GSK_BLUR_Y);
|
|
|
|
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, GSK_BLUR_X | GSK_BLUR_Y);
|
|
|
|
cairo_restore (cr);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
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_get_extents (const GtkCssValue *shadow,
|
|
GtkBorder *border)
|
|
{
|
|
gdouble hoffset, voffset, spread, radius, clip_radius;
|
|
|
|
spread = _gtk_css_number_value_get (shadow->spread, 0);
|
|
radius = _gtk_css_number_value_get (shadow->radius, 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 (0, ceil (clip_radius + spread - voffset));
|
|
border->right = MAX (0, ceil (clip_radius + spread + hoffset));
|
|
border->bottom = MAX (0, ceil (clip_radius + spread + voffset));
|
|
border->left = MAX (0, ceil (clip_radius + spread - hoffset));
|
|
}
|
|
|
|
void
|
|
gtk_css_shadow_value_get_shadow (const GtkCssValue *value,
|
|
GskShadow *shadow)
|
|
{
|
|
shadow->color = *_gtk_css_rgba_value_get_rgba (value->color);
|
|
shadow->dx = _gtk_css_number_value_get (value->hoffset, 0);
|
|
shadow->dy = _gtk_css_number_value_get (value->voffset, 0);
|
|
shadow->radius = _gtk_css_number_value_get (value->radius, 0);
|
|
}
|
|
|
|
static gboolean
|
|
has_empty_clip (cairo_t *cr)
|
|
{
|
|
double x1, y1, x2, y2;
|
|
|
|
cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
|
|
return x1 == x2 && y1 == y2;
|
|
}
|
|
|
|
static void
|
|
draw_shadow (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
GskRoundedRect *box,
|
|
GskRoundedRect *clip_box,
|
|
GskBlurFlags blur_flags)
|
|
{
|
|
cairo_t *shadow_cr;
|
|
gboolean do_blur;
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
do_blur = (blur_flags & (GSK_BLUR_X | GSK_BLUR_Y)) != 0;
|
|
if (do_blur)
|
|
shadow_cr = gtk_css_shadow_value_start_drawing (shadow, cr, blur_flags);
|
|
else
|
|
shadow_cr = cr;
|
|
|
|
cairo_set_fill_rule (shadow_cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (box, shadow_cr);
|
|
if (shadow->inset)
|
|
_gtk_rounded_box_clip_path (clip_box, shadow_cr);
|
|
|
|
cairo_fill (shadow_cr);
|
|
|
|
if (do_blur)
|
|
gtk_css_shadow_value_finish_drawing (shadow, shadow_cr, blur_flags);
|
|
}
|
|
|
|
typedef struct {
|
|
double radius;
|
|
graphene_size_t corner;
|
|
} CornerMask;
|
|
|
|
static guint
|
|
corner_mask_hash (CornerMask *mask)
|
|
{
|
|
return ((guint)mask->radius << 24) ^
|
|
((guint)(mask->corner.width*4)) << 12 ^
|
|
((guint)(mask->corner.height*4)) << 0;
|
|
}
|
|
|
|
static gboolean
|
|
corner_mask_equal (CornerMask *mask1,
|
|
CornerMask *mask2)
|
|
{
|
|
return
|
|
mask1->radius == mask2->radius &&
|
|
mask1->corner.width == mask2->corner.width &&
|
|
mask1->corner.height == mask2->corner.height;
|
|
}
|
|
|
|
static void
|
|
draw_shadow_corner (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
GskRoundedRect *box,
|
|
GskRoundedRect *clip_box,
|
|
GskCorner corner,
|
|
cairo_rectangle_int_t *drawn_rect)
|
|
{
|
|
gdouble radius, clip_radius;
|
|
int x1, x2, x3, y1, y2, y3, x, y;
|
|
GskRoundedRect corner_box;
|
|
cairo_t *mask_cr;
|
|
cairo_surface_t *mask;
|
|
cairo_pattern_t *pattern;
|
|
cairo_matrix_t matrix;
|
|
double sx, sy;
|
|
static GHashTable *corner_mask_cache = NULL;
|
|
double max_other;
|
|
CornerMask key;
|
|
gboolean overlapped;
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
overlapped = FALSE;
|
|
if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_BOTTOM_LEFT)
|
|
{
|
|
x1 = floor (box->bounds.origin.x - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->corner[corner].width + clip_radius);
|
|
x = x1;
|
|
sx = 1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_RIGHT].width, box->corner[GSK_CORNER_BOTTOM_RIGHT].width);
|
|
x3 = floor (box->bounds.origin.x + box->bounds.size.width - max_other - clip_radius);
|
|
if (x2 > x3)
|
|
overlapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (box->bounds.origin.x + box->bounds.size.width - box->corner[corner].width - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
x = x2;
|
|
sx = -1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].width, box->corner[GSK_CORNER_BOTTOM_LEFT].width);
|
|
x3 = ceil (box->bounds.origin.x + max_other + clip_radius);
|
|
if (x3 > x1)
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_TOP_RIGHT)
|
|
{
|
|
y1 = floor (box->bounds.origin.y - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->corner[corner].height + clip_radius);
|
|
y = y1;
|
|
sy = 1;
|
|
max_other = MAX(box->corner[GSK_CORNER_BOTTOM_LEFT].height, box->corner[GSK_CORNER_BOTTOM_RIGHT].height);
|
|
y3 = floor (box->bounds.origin.y + box->bounds.size.height - max_other - clip_radius);
|
|
if (y2 > y3)
|
|
overlapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
y1 = floor (box->bounds.origin.y + box->bounds.size.height - box->corner[corner].height - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
y = y2;
|
|
sy = -1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].height, box->corner[GSK_CORNER_TOP_RIGHT].height);
|
|
y3 = ceil (box->bounds.origin.y + max_other + clip_radius);
|
|
if (y3 > y1)
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
drawn_rect->x = x1;
|
|
drawn_rect->y = y1;
|
|
drawn_rect->width = x2 - x1;
|
|
drawn_rect->height = y2 - y1;
|
|
|
|
cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
|
|
cairo_clip (cr);
|
|
|
|
if (shadow->inset || overlapped)
|
|
{
|
|
/* Fall back to generic path if inset or if the corner radius
|
|
runs into each other */
|
|
draw_shadow (shadow, cr, box, clip_box, GSK_BLUR_X | GSK_BLUR_Y);
|
|
return;
|
|
}
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
/* At this point we're drawing a blurred outset corner. The only
|
|
* things that affect the output of the blurred mask in this case
|
|
* is:
|
|
*
|
|
* What corner this is, which defines the orientation (sx,sy)
|
|
* and position (x,y)
|
|
*
|
|
* The blur radius (which also defines the clip_radius)
|
|
*
|
|
* The the horizontal and vertical corner radius
|
|
*
|
|
* We apply the first position and orientation when drawing the
|
|
* mask, so we cache rendered masks based on the blur radius and the
|
|
* corner radius.
|
|
*/
|
|
if (corner_mask_cache == NULL)
|
|
corner_mask_cache = g_hash_table_new_full ((GHashFunc)corner_mask_hash,
|
|
(GEqualFunc)corner_mask_equal,
|
|
g_free, (GDestroyNotify)cairo_surface_destroy);
|
|
|
|
key.radius = radius;
|
|
key.corner = box->corner[corner];
|
|
|
|
mask = g_hash_table_lookup (corner_mask_cache, &key);
|
|
if (mask == NULL)
|
|
{
|
|
mask = cairo_surface_create_similar_image (cairo_get_target (cr), CAIRO_FORMAT_A8,
|
|
drawn_rect->width + clip_radius,
|
|
drawn_rect->height + clip_radius);
|
|
mask_cr = cairo_create (mask);
|
|
_gtk_rounded_box_init_rect (&corner_box, clip_radius, clip_radius, 2*drawn_rect->width, 2*drawn_rect->height);
|
|
corner_box.corner[0] = box->corner[corner];
|
|
gsk_rounded_rect_path (&corner_box, mask_cr);
|
|
cairo_fill (mask_cr);
|
|
gsk_cairo_blur_surface (mask, radius, GSK_BLUR_X | GSK_BLUR_Y);
|
|
cairo_destroy (mask_cr);
|
|
g_hash_table_insert (corner_mask_cache, g_memdup (&key, sizeof (key)), mask);
|
|
}
|
|
|
|
gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
|
|
pattern = cairo_pattern_create_for_surface (mask);
|
|
cairo_matrix_init_identity (&matrix);
|
|
cairo_matrix_scale (&matrix, sx, sy);
|
|
cairo_matrix_translate (&matrix, -x, -y);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
cairo_mask (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
draw_shadow_side (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
GskRoundedRect *box,
|
|
GskRoundedRect *clip_box,
|
|
GtkCssSide side,
|
|
cairo_rectangle_int_t *drawn_rect)
|
|
{
|
|
GskBlurFlags blur_flags = GSK_BLUR_REPEAT;
|
|
gdouble radius, clip_radius;
|
|
int x1, x2, y1, y2;
|
|
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
if (side == GTK_CSS_TOP || side == GTK_CSS_BOTTOM)
|
|
{
|
|
blur_flags |= GSK_BLUR_Y;
|
|
x1 = floor (box->bounds.origin.x - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
}
|
|
else if (side == GTK_CSS_LEFT)
|
|
{
|
|
x1 = floor (box->bounds.origin.x -clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + clip_radius);
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (box->bounds.origin.x + box->bounds.size.width -clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
}
|
|
|
|
if (side == GTK_CSS_LEFT || side == GTK_CSS_RIGHT)
|
|
{
|
|
blur_flags |= GSK_BLUR_X;
|
|
y1 = floor (box->bounds.origin.y - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
}
|
|
else if (side == GTK_CSS_TOP)
|
|
{
|
|
y1 = floor (box->bounds.origin.y -clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + clip_radius);
|
|
}
|
|
else
|
|
{
|
|
y1 = floor (box->bounds.origin.y + box->bounds.size.height -clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
}
|
|
|
|
drawn_rect->x = x1;
|
|
drawn_rect->y = y1;
|
|
drawn_rect->width = x2 - x1;
|
|
drawn_rect->height = y2 - y1;
|
|
|
|
cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
|
|
cairo_clip (cr);
|
|
draw_shadow (shadow, cr, box, clip_box, blur_flags);
|
|
}
|
|
|
|
void
|
|
_gtk_css_shadow_value_paint_box (const GtkCssValue *shadow,
|
|
cairo_t *cr,
|
|
const GskRoundedRect*padding_box)
|
|
{
|
|
GskRoundedRect box, clip_box;
|
|
double spread, radius, clip_radius, x, y, outside;
|
|
double x1c, y1c, x2c, y2c;
|
|
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
|
|
return;
|
|
|
|
cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
|
|
if ((shadow->inset && !gsk_rounded_rect_intersects_rect (padding_box, &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c))) ||
|
|
(!shadow->inset && gsk_rounded_rect_contains_rect (padding_box, &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c))))
|
|
return;
|
|
|
|
cairo_save (cr);
|
|
|
|
spread = _gtk_css_number_value_get (shadow->spread, 0);
|
|
radius = _gtk_css_number_value_get (shadow->radius, 0);
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
x = _gtk_css_number_value_get (shadow->hoffset, 0);
|
|
y = _gtk_css_number_value_get (shadow->voffset, 0);
|
|
|
|
if (shadow->inset)
|
|
{
|
|
gsk_rounded_rect_path (padding_box, cr);
|
|
cairo_clip (cr);
|
|
}
|
|
else
|
|
{
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (padding_box, cr);
|
|
outside = spread + clip_radius + MAX (fabs (x), fabs (y));
|
|
clip_box = *padding_box;
|
|
gsk_rounded_rect_shrink (&clip_box, -outside, -outside, -outside, -outside);
|
|
_gtk_rounded_box_clip_path (&clip_box, cr);
|
|
|
|
cairo_clip (cr);
|
|
}
|
|
|
|
box = *padding_box;
|
|
gsk_rounded_rect_offset (&box, x, y);
|
|
|
|
if (shadow->inset)
|
|
gsk_rounded_rect_shrink (&box, spread, spread, spread, spread);
|
|
else /* Outset */
|
|
gsk_rounded_rect_shrink (&box, -spread, -spread, -spread, -spread);
|
|
|
|
clip_box = *padding_box;
|
|
gsk_rounded_rect_shrink (&clip_box, -clip_radius, -clip_radius, -clip_radius, -clip_radius);
|
|
|
|
if (!needs_blur (shadow))
|
|
draw_shadow (shadow, cr, &box, &clip_box, GSK_BLUR_NONE);
|
|
else
|
|
{
|
|
int i;
|
|
cairo_region_t *remaining;
|
|
cairo_rectangle_int_t r;
|
|
|
|
/* For the blurred case we divide the rendering into 9 parts,
|
|
* 4 of the corners, 4 for the horizonat/vertical lines and
|
|
* one for the interior. We make the non-interior parts
|
|
* large enought to fit the full radius of the blur, so that
|
|
* the interior part can be drawn solidly.
|
|
*/
|
|
|
|
if (shadow->inset)
|
|
{
|
|
/* In the inset case we want to paint the whole clip-box.
|
|
* We could remove the part of "box" where the blur doesn't
|
|
* reach, but computing that is a bit tricky since the
|
|
* rounded corners are on the "inside" of it. */
|
|
r.x = floor (clip_box.bounds.origin.x);
|
|
r.y = floor (clip_box.bounds.origin.y);
|
|
r.width = ceil (clip_box.bounds.origin.x + clip_box.bounds.size.width) - r.x;
|
|
r.height = ceil (clip_box.bounds.origin.y + clip_box.bounds.size.height) - r.y;
|
|
remaining = cairo_region_create_rectangle (&r);
|
|
}
|
|
else
|
|
{
|
|
/* In the outset case we want to paint the entire box, plus as far
|
|
* as the radius reaches from it */
|
|
r.x = floor (box.bounds.origin.x - clip_radius);
|
|
r.y = floor (box.bounds.origin.y - clip_radius);
|
|
r.width = ceil (box.bounds.origin.x + box.bounds.size.width + clip_radius) - r.x;
|
|
r.height = ceil (box.bounds.origin.y + box.bounds.size.height + clip_radius) - r.y;
|
|
|
|
remaining = cairo_region_create_rectangle (&r);
|
|
}
|
|
|
|
/* First do the corners of box */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_corner (shadow, cr, &box, &clip_box, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the sides */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_side (shadow, cr, &box, &clip_box, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the rest, which needs no blurring */
|
|
|
|
cairo_save (cr);
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow (shadow, cr, &box, &clip_box, GSK_BLUR_NONE);
|
|
cairo_restore (cr);
|
|
|
|
cairo_region_destroy (remaining);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
void
|
|
gtk_css_shadow_value_snapshot_outset (const GtkCssValue *shadow,
|
|
GtkSnapshot *snapshot,
|
|
const GskRoundedRect *border_box)
|
|
{
|
|
GskRoundedRect outline;
|
|
GskRenderNode *node;
|
|
int off_x, off_y;
|
|
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
|
|
return;
|
|
|
|
gtk_snapshot_get_offset (snapshot, &off_x, &off_y);
|
|
gsk_rounded_rect_init_copy (&outline, border_box);
|
|
gsk_rounded_rect_offset (&outline, off_x, off_y);
|
|
|
|
node = gsk_outset_shadow_node_new (&outline,
|
|
_gtk_css_rgba_value_get_rgba (shadow->color),
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0),
|
|
_gtk_css_number_value_get (shadow->spread, 0),
|
|
_gtk_css_number_value_get (shadow->radius, 0));
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
gsk_render_node_unref (node);
|
|
}
|
|
|
|
void
|
|
gtk_css_shadow_value_snapshot_inset (const GtkCssValue *shadow,
|
|
GtkSnapshot *snapshot,
|
|
const GskRoundedRect*padding_box)
|
|
{
|
|
GskRoundedRect outline;
|
|
GskRenderNode *node;
|
|
int off_x, off_y;
|
|
|
|
g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
|
|
return;
|
|
|
|
gtk_snapshot_get_offset (snapshot, &off_x, &off_y);
|
|
gsk_rounded_rect_init_copy (&outline, padding_box);
|
|
gsk_rounded_rect_offset (&outline, off_x, off_y);
|
|
|
|
node = gsk_inset_shadow_node_new (&outline,
|
|
_gtk_css_rgba_value_get_rgba (shadow->color),
|
|
_gtk_css_number_value_get (shadow->hoffset, 0),
|
|
_gtk_css_number_value_get (shadow->voffset, 0),
|
|
_gtk_css_number_value_get (shadow->spread, 0),
|
|
_gtk_css_number_value_get (shadow->radius, 0));
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
gsk_render_node_unref (node);
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_shadow_value_is_clear (const GtkCssValue *shadow)
|
|
{
|
|
return gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color));
|
|
}
|
|
|