gtkcssshadowvalue.c 15.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* 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
Javier Jardón's avatar
Javier Jardón committed
17
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 19 20 21
 */

#include "config.h"

22
#include "gtkcssshadowvalueprivate.h"
23

24
#include "gtkcairoblurprivate.h"
25
#include "gtkcsscolorvalueprivate.h"
26
#include "gtkcssnumbervalueprivate.h"
27
#include "gtkcssrgbavalueprivate.h"
28
#include "gtkstylecontextprivate.h"
29
#include "gtkthemingengineprivate.h"
30
#include "gtkpango.h"
31

32 33 34
struct _GtkCssValue {
  GTK_CSS_VALUE_BASE
  guint inset :1;
35

36 37 38 39
  GtkCssValue *hoffset;
  GtkCssValue *voffset;
  GtkCssValue *radius;
  GtkCssValue *spread;
40

41
  GtkCssValue *color;
42 43
};

44 45 46 47 48 49 50
static GtkCssValue *    gtk_css_shadow_value_new (GtkCssValue *hoffset,
                                                  GtkCssValue *voffset,
                                                  GtkCssValue *radius,
                                                  GtkCssValue *spread,
                                                  gboolean     inset,
                                                  GtkCssValue *color);

51 52
static void
gtk_css_value_shadow_free (GtkCssValue *shadow)
53
{
54 55 56 57
  _gtk_css_value_unref (shadow->hoffset);
  _gtk_css_value_unref (shadow->voffset);
  _gtk_css_value_unref (shadow->radius);
  _gtk_css_value_unref (shadow->spread);
58
  _gtk_css_value_unref (shadow->color);
59

60
  g_slice_free (GtkCssValue, shadow);
61
}
62

63
static GtkCssValue *
64 65 66 67 68 69
gtk_css_value_shadow_compute (GtkCssValue             *shadow,
                              guint                    property_id,
                              GtkStyleProviderPrivate *provider,
                              GtkCssComputedValues    *values,
                              GtkCssComputedValues    *parent_values,
                              GtkCssDependencies      *dependencies)
70
{
71 72 73 74
  GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
  GtkCssDependencies child_deps;

  child_deps = 0;
75
  hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, provider, values, parent_values, &child_deps);
76 77 78
  *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);

  child_deps = 0;
79
  voffset = _gtk_css_value_compute (shadow->voffset, property_id, provider, values, parent_values, &child_deps);
80 81 82
  *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);

  child_deps = 0;
83
  radius = _gtk_css_value_compute (shadow->radius, property_id, provider, values, parent_values, &child_deps);
84 85 86
  *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);

  child_deps = 0;
87
  spread = _gtk_css_value_compute (shadow->spread, property_id, provider, values, parent_values, &child_deps),
88 89 90
  *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);

  child_deps = 0;
91
  color = _gtk_css_value_compute (shadow->color, property_id, provider, values, parent_values, &child_deps);
92 93 94
  *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);

  return gtk_css_shadow_value_new (hoffset, voffset, radius, spread, shadow->inset, color);
95 96
}

97 98 99 100
static gboolean
gtk_css_value_shadow_equal (const GtkCssValue *shadow1,
                            const GtkCssValue *shadow2)
{
101 102 103 104 105 106
  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);
107 108
}

109 110 111
static GtkCssValue *
gtk_css_value_shadow_transition (GtkCssValue *start,
                                 GtkCssValue *end,
112
                                 guint        property_id,
113 114
                                 double       progress)
{
115 116 117
  if (start->inset != end->inset)
    return NULL;

118 119 120 121
  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),
122
                                   start->inset,
123
                                   _gtk_css_value_transition (start->color, end->color, property_id, progress));
124 125
}

126 127 128
static void
gtk_css_value_shadow_print (const GtkCssValue *shadow,
                            GString           *string)
129
{
130 131 132 133 134 135 136 137 138 139
  _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, ' ');
    }
140

141 142 143 144 145
  if (_gtk_css_number_value_get (shadow->spread, 100) != 0)
    {
      _gtk_css_value_print (shadow->spread, string);
      g_string_append_c (string, ' ');
    }
146

147
  _gtk_css_value_print (shadow->color, string);
148

149 150
  if (shadow->inset)
    g_string_append (string, " inset");
151 152 153

}

154 155
static const GtkCssValueClass GTK_CSS_VALUE_SHADOW = {
  gtk_css_value_shadow_free,
156
  gtk_css_value_shadow_compute,
157
  gtk_css_value_shadow_equal,
158
  gtk_css_value_shadow_transition,
159 160 161
  gtk_css_value_shadow_print
};

162
static GtkCssValue *
163 164 165 166 167
gtk_css_shadow_value_new (GtkCssValue *hoffset,
                          GtkCssValue *voffset,
                          GtkCssValue *radius,
                          GtkCssValue *spread,
                          gboolean     inset,
168
                          GtkCssValue *color)
169
{
170 171 172 173 174 175 176 177 178
  GtkCssValue *retval;

  retval = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_SHADOW);

  retval->hoffset = hoffset;
  retval->voffset = voffset;
  retval->radius = radius;
  retval->spread = spread;
  retval->inset = inset;
179
  retval->color = color;
180 181 182

  return retval;
}                  
183

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
GtkCssValue *
_gtk_css_shadow_value_new_for_transition (GtkCssValue *target)
{
  GdkRGBA transparent = { 0, 0, 0, 0 };

  g_return_val_if_fail (target->class == &GTK_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));
}

199 200 201 202 203 204 205 206 207
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, '}');
}

208 209
GtkCssValue *
_gtk_css_shadow_value_parse (GtkCssParser *parser)
210
{
211 212 213 214 215 216 217 218 219 220
  enum {
    HOFFSET,
    VOFFSET,
    RADIUS,
    SPREAD,
    COLOR,
    N_VALUES
  };
  GtkCssValue *values[N_VALUES] = { NULL, };
  gboolean inset;
221 222
  guint i;

223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
  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)
      {
274
        values[COLOR] = _gtk_css_color_value_parse (parser);
275

276
        if (values[COLOR] == NULL)
277 278 279 280 281 282 283 284 285 286 287 288 289
          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)
290
    values[COLOR] = _gtk_css_color_value_new_current_color ();
291 292 293 294 295 296 297

  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++)
298
    {
299 300
      if (values[i])
        _gtk_css_value_unref (values[i]);
301
    }
302

303
  return NULL;
304 305
}

306 307 308 309 310 311
static const cairo_user_data_key_t shadow_key;

static cairo_t *
gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
                                    cairo_t           *cr)
{
312
  cairo_rectangle_int_t clip_rect;
313 314 315 316 317 318 319 320
  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;

321
  gdk_cairo_get_clip_rectangle (cr, &clip_rect);
322 323

  /* Create a larger surface to center the blur. */
324 325 326 327
  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);
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
  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;
347 348
  cairo_t *original_cr;
  cairo_surface_t *surface;
349 350 351 352 353

  radius = _gtk_css_number_value_get (shadow->radius, 0);
  if (radius == 0.0)
    return cr;

354
  surface = cairo_get_target (cr);
355 356 357
  original_cr = cairo_get_user_data (cr, &shadow_key);

  /* Blur the surface. */
358 359 360
  _gtk_cairo_blur_surface (surface, radius);

  cairo_set_source_surface (original_cr, surface, 0, 0);
361 362 363
  cairo_paint (original_cr);

  cairo_destroy (cr);
364
  cairo_surface_destroy (surface);
365 366 367 368

  return original_cr;
}

369
void
370 371 372
_gtk_css_shadow_value_paint_layout (const GtkCssValue *shadow,
                                    cairo_t           *cr,
                                    PangoLayout       *layout)
373
{
374
  g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
375

376 377 378
  if (!cairo_has_current_point (cr))
    cairo_move_to (cr, 0, 0);

379
  cairo_save (cr);
380

381 382 383
  cairo_rel_move_to (cr, 
                     _gtk_css_number_value_get (shadow->hoffset, 0),
                     _gtk_css_number_value_get (shadow->voffset, 0));
384 385 386

  cr = gtk_css_shadow_value_start_drawing (shadow, cr);

387
  gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
388
  _gtk_pango_fill_layout (cr, layout);
389

390 391
  cr = gtk_css_shadow_value_finish_drawing (shadow, cr);

392 393 394
  cairo_rel_move_to (cr,
                     - _gtk_css_number_value_get (shadow->hoffset, 0),
                     - _gtk_css_number_value_get (shadow->voffset, 0));
395
  cairo_restore (cr);
396 397
}

398
void
399 400
_gtk_css_shadow_value_paint_icon (const GtkCssValue *shadow,
			          cairo_t           *cr)
401 402 403
{
  cairo_pattern_t *pattern;

404
  g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
405

406 407
  cairo_save (cr);
  pattern = cairo_pattern_reference (cairo_get_source (cr));
408 409 410

  cr = gtk_css_shadow_value_start_drawing (shadow, cr);

411
  gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
412

413 414 415
  cairo_translate (cr,
                   _gtk_css_number_value_get (shadow->hoffset, 0),
                   _gtk_css_number_value_get (shadow->voffset, 0));
416
  cairo_mask (cr, pattern);
417

418 419
  cr = gtk_css_shadow_value_finish_drawing (shadow, cr);

420 421
  cairo_restore (cr);
  cairo_pattern_destroy (pattern);
422
}
423 424

void
425 426 427 428
_gtk_css_shadow_value_paint_spinner (const GtkCssValue *shadow,
                                     cairo_t           *cr,
                                     gdouble            radius,
                                     gdouble            progress)
429
{
430
  g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
431

432
  cairo_save (cr);
433

434 435
  cr = gtk_css_shadow_value_start_drawing (shadow, cr);

436 437 438
  cairo_translate (cr,
                   _gtk_css_number_value_get (shadow->hoffset, 0),
                   _gtk_css_number_value_get (shadow->voffset, 0));
439 440
  _gtk_theming_engine_paint_spinner (cr,
                                     radius, progress,
441
                                     _gtk_css_rgba_value_get_rgba (shadow->color));
442

443 444
  cr = gtk_css_shadow_value_finish_drawing (shadow, cr);

445
  cairo_restore (cr);
446
}
447 448

void
449 450 451
_gtk_css_shadow_value_paint_box (const GtkCssValue   *shadow,
                                 cairo_t             *cr,
                                 const GtkRoundedBox *padding_box)
452
{
453 454
  GtkRoundedBox box, clip_box;
  double spread, radius;
455 456

  g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
457 458 459 460 461 462

  cairo_save (cr);

  _gtk_rounded_box_path (padding_box, cr);
  cairo_clip (cr);

463
  box = *padding_box;
464 465 466 467 468
  _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);
  _gtk_rounded_box_shrink (&box, spread, spread, spread, spread);
469

470 471 472 473 474 475 476
  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);
477
  _gtk_rounded_box_path (&box, cr);
478
  _gtk_rounded_box_clip_path (&clip_box, cr);
479

480
  gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
481
  cairo_fill (cr);
482

483 484
  cr = gtk_css_shadow_value_finish_drawing (shadow, cr);

485 486
  cairo_restore (cr);
}