gtkrenderbackground.c 16.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* GTK - The GIMP Toolkit
 * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
 * Copyright (C) 2011 Red Hat, Inc.
 * 
 * Authors: Carlos Garnacho <carlosg@gnome.org>
 *          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
19
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 21
 */

22 23
#include "config.h"

24
#include "gtkrenderbackgroundprivate.h"
25

26
#include "gtkcssarrayvalueprivate.h"
27
#include "gtkcssbgsizevalueprivate.h"
28
#include "gtkcsscornervalueprivate.h"
29
#include "gtkcssenumvalueprivate.h"
30
#include "gtkcssimagevalueprivate.h"
31
#include "gtkcssnumbervalueprivate.h"
32
#include "gtkcssshadowsvalueprivate.h"
33
#include "gtkcsspositionvalueprivate.h"
34
#include "gtkcssrepeatvalueprivate.h"
35
#include "gtkcssrgbavalueprivate.h"
36
#include "gtkcssstyleprivate.h"
37
#include "gtkcsstypesprivate.h"
38

39 40
#include <math.h>

41 42
#include <gdk/gdk.h>

43 44 45 46 47
/* this is in case round() is not provided by the compiler, 
 * such as in the case of C89 compilers, like MSVC
 */
#include "fallback-c89.c"

48 49
typedef struct _GtkThemingBackground GtkThemingBackground;

50 51
#define N_BOXES (3)

52
struct _GtkThemingBackground {
53
  GtkCssStyle *style;
54

55
  GtkRoundedBox boxes[N_BOXES];
56 57
};

58 59 60
static void
_gtk_theming_background_paint_color (GtkThemingBackground *bg,
                                     cairo_t              *cr,
61
                                     const GdkRGBA        *bg_color,
62 63 64 65 66
                                     GtkCssValue          *background_image)
{
  gint n_values = _gtk_css_array_value_get_n_values (background_image);
  GtkCssArea clip = _gtk_css_area_value_get 
    (_gtk_css_array_value_get_nth 
67
     (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_CLIP), 
68 69
      n_values - 1));

70
  _gtk_rounded_box_path (&bg->boxes[clip], cr);
71
  gdk_cairo_set_source_rgba (cr, bg_color);
72
  cairo_fill (cr);
73 74
}

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
static gboolean
_gtk_theming_background_needs_push_group (GtkCssStyle *style)
{
  GtkCssValue *blend_modes;
  gint i;

  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;
}

100 101
static void
_gtk_theming_background_paint_layer (GtkThemingBackground *bg,
102
                                     guint                 idx,
103 104
                                     cairo_t              *cr,
                                     GtkCssBlendMode       blend_mode)
105
{
106 107
  GtkCssRepeatStyle hrepeat, vrepeat;
  const GtkCssValue *pos, *repeat;
108
  GtkCssImage *image;
109
  const GtkRoundedBox *origin;
110 111 112
  double image_width, image_height;
  double width, height;

113 114
  pos = _gtk_css_array_value_get_nth (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_POSITION), idx);
  repeat = _gtk_css_array_value_get_nth (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_REPEAT), idx);
115 116
  hrepeat = _gtk_css_background_repeat_value_get_x (repeat);
  vrepeat = _gtk_css_background_repeat_value_get_y (repeat);
117 118
  image = _gtk_css_image_value_get_image (
              _gtk_css_array_value_get_nth (
119
                  gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_IMAGE),
120
                  idx));
121
  origin = &bg->boxes[
122 123
               _gtk_css_area_value_get (
                   _gtk_css_array_value_get_nth (
124
                       gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_ORIGIN),
125
                       idx))];
126 127
  width = origin->box.width;
  height = origin->box.height;
128

129
  if (image == NULL || width <= 0 || height <= 0)
130 131
    return;

132
  _gtk_css_bg_size_value_compute_size (_gtk_css_array_value_get_nth (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_SIZE), idx),
133
                                       image,
134 135 136 137 138
                                       width,
                                       height,
                                       &image_width,
                                       &image_height);

139 140 141
  if (image_width <= 0 || image_height <= 0)
    return;

142 143 144 145 146 147
  /* optimization */
  if (image_width == width)
    hrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT;
  if (image_height == height)
    vrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT;

148 149 150

  cairo_save (cr);

151
  _gtk_rounded_box_path (
152
      &bg->boxes[
153 154
          _gtk_css_area_value_get (
              _gtk_css_array_value_get_nth (
155
                  gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_CLIP),
156
                  idx))],
157
      cr);
158 159 160
  cairo_clip (cr);


161
  cairo_translate (cr, origin->box.x, origin->box.y);
162

163 164 165 166 167 168 169
  /*
   * 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));


170 171 172 173 174 175
  if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT && vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
    {
      cairo_translate (cr,
                       _gtk_css_position_value_get_x (pos, width - image_width),
                       _gtk_css_position_value_get_y (pos, height - image_height));
      /* shortcut for normal case */
176
      _gtk_css_image_draw (image, cr, image_width, image_height);
177 178
    }
  else
179
    {
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
      int surface_width, surface_height;
      cairo_rectangle_t fill_rect;
      cairo_surface_t *surface;
      cairo_t *cr2;

      /* If ‘background-repeat’ is ‘round’ for one (or both) dimensions,
       * there is a second step. The UA must scale the image in that
       * dimension (or both dimensions) so that it fits a whole number of
       * times in the background positioning area. In the case of the width
       * (height is analogous):
       *
       * If X ≠ 0 is the width of the image after step one and W is the width
       * of the background positioning area, then the rounded width
       * X' = W / round(W / X) where round() is a function that returns the
       * nearest natural number (integer greater than zero). 
       *
       * If ‘background-repeat’ is ‘round’ for one dimension only and if
       * ‘background-size’ is ‘auto’ for the other dimension, then there is
       * a third step: that other dimension is scaled so that the original
       * aspect ratio is restored. 
       */
      if (hrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
        {
          double n = round (width / image_width);

          n = MAX (1, n);

          if (vrepeat != GTK_CSS_REPEAT_STYLE_ROUND
              /* && vsize == auto (it is by default) */)
            image_height *= width / (image_width * n);
          image_width = width / n;
        }
      if (vrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
213
        {
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
          double n = round (height / image_height);

          n = MAX (1, n);

          if (hrepeat != GTK_CSS_REPEAT_STYLE_ROUND
              /* && hsize == auto (it is by default) */)
            image_width *= height / (image_height * n);
          image_height = height / n;
        }

      /* if hrepeat or vrepeat is 'space', we create a somewhat larger surface
       * to store the extra space. */
      if (hrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
        {
          double n = floor (width / image_width);
          surface_width = n ? round (width / n) : 0;
230 231
        }
      else
232 233 234
        surface_width = round (image_width);

      if (vrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
235
        {
236 237
          double n = floor (height / image_height);
          surface_height = n ? round (height / n) : 0;
238
        }
239 240 241 242 243 244 245 246 247 248
      else
        surface_height = round (image_height);

      surface = cairo_surface_create_similar (cairo_get_target (cr),
                                              CAIRO_CONTENT_COLOR_ALPHA,
                                              surface_width, surface_height);
      cr2 = cairo_create (surface);
      cairo_translate (cr2,
                       0.5 * (surface_width - image_width),
                       0.5 * (surface_height - image_height));
249
      _gtk_css_image_draw (image, cr2, image_width, image_height);
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
      cairo_destroy (cr2);

      cairo_set_source_surface (cr, surface,
                                _gtk_css_position_value_get_x (pos, width - image_width),
                                _gtk_css_position_value_get_y (pos, height - image_height));
      cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
      cairo_surface_destroy (surface);

      if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
        {
          fill_rect.x = _gtk_css_position_value_get_x (pos, width - image_width);
          fill_rect.width = image_width;
        }
      else
        {
          fill_rect.x = 0;
          fill_rect.width = width;
        }

      if (vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
        {
          fill_rect.y = _gtk_css_position_value_get_y (pos, height - image_height);
          fill_rect.height = image_height;
        }
      else
        {
          fill_rect.y = 0;
          fill_rect.height = height;
        }

      cairo_rectangle (cr, fill_rect.x, fill_rect.y,
                       fill_rect.width, fill_rect.height);
      cairo_fill (cr);
283 284
    }

285 286 287 288 289 290
  /*
   * 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);
291

292
  cairo_restore (cr);
293 294 295
}

static void
296 297 298 299
_gtk_theming_background_init_style (GtkThemingBackground *bg,
                                    double                width,
                                    double                height,
                                    GtkJunctionSides      junction)
300
{
301
  GtkBorder border, padding;
302

303 304 305 306 307 308 309 310
  border.top = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_TOP_WIDTH), 100);
  border.right = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_RIGHT_WIDTH), 100);
  border.bottom = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_BOTTOM_WIDTH), 100);
  border.left = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_LEFT_WIDTH), 100);
  padding.top = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_TOP), 100);
  padding.right = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_RIGHT), 100);
  padding.bottom = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_BOTTOM), 100);
  padding.left = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_LEFT), 100);
311 312 313 314 315 316 317 318 319

  /* In the CSS box model, by default the background positioning area is
   * the padding-box, i.e. all the border-box minus the borders themselves,
   * which determines also its default size, see
   * http://dev.w3.org/csswg/css3-background/#background-origin
   *
   * In the future we might want to support different origins or clips, but
   * right now we just shrink to the default.
   */
320
  _gtk_rounded_box_init_rect (&bg->boxes[GTK_CSS_AREA_BORDER_BOX], 0, 0, width, height);
321
  _gtk_rounded_box_apply_border_radius_for_style (&bg->boxes[GTK_CSS_AREA_BORDER_BOX], bg->style, junction);
322

323 324
  bg->boxes[GTK_CSS_AREA_PADDING_BOX] = bg->boxes[GTK_CSS_AREA_BORDER_BOX];
  _gtk_rounded_box_shrink (&bg->boxes[GTK_CSS_AREA_PADDING_BOX],
325 326
			   border.top, border.right,
			   border.bottom, border.left);
327

328 329
  bg->boxes[GTK_CSS_AREA_CONTENT_BOX] = bg->boxes[GTK_CSS_AREA_PADDING_BOX];
  _gtk_rounded_box_shrink (&bg->boxes[GTK_CSS_AREA_CONTENT_BOX],
330 331
			   padding.top, padding.right,
			   padding.bottom, padding.left);
332 333 334
}

void
335 336 337 338 339 340 341
gtk_css_style_render_background (GtkCssStyle      *style,
                                 cairo_t          *cr,
                                 gdouble           x,
                                 gdouble           y,
                                 gdouble           width,
                                 gdouble           height,
                                 GtkJunctionSides  junction)
342
{
343 344 345
  GtkThemingBackground bg;
  gint idx;
  GtkCssValue *background_image;
346
  GtkCssValue *blend_modes;
347
  GtkCssValue *box_shadow;
348
  const GdkRGBA *bg_color;
349 350
  gboolean needs_push_group;
  gint number_of_layers;
351

352
  background_image = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_IMAGE);
353
  blend_modes = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE);
354 355
  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);
356 357 358 359 360 361 362

  /* This is the common default case of no background */
  if (gtk_rgba_is_clear (bg_color) &&
      _gtk_css_array_value_get_n_values (background_image) == 1 &&
      _gtk_css_image_value_get_image (_gtk_css_array_value_get_nth (background_image, 0)) == NULL &&
      _gtk_css_shadows_value_is_none (box_shadow))
    return;
363

364 365
  bg.style = style;
  _gtk_theming_background_init_style (&bg, width, height, junction);
366

367
  cairo_save (cr);
368
  cairo_translate (cr, x, y);
369

370 371 372 373 374 375
  /* Outset shadows */
  _gtk_css_shadows_value_paint_box (box_shadow,
                                    cr,
                                    &bg.boxes[GTK_CSS_AREA_BORDER_BOX],
                                    FALSE);

376 377 378 379 380 381 382 383 384 385 386
  /*
   * 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);
387
      cairo_rectangle (cr, 0, 0, width, height);
388 389 390 391
      cairo_clip (cr);
      cairo_push_group (cr);
    }

392
  _gtk_theming_background_paint_color (&bg, cr, bg_color, background_image);
393

394 395 396
  number_of_layers = _gtk_css_array_value_get_n_values (background_image);

  for (idx = number_of_layers - 1; idx >= 0; idx--)
397
    {
398 399 400 401 402
      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);
403 404
    }

405 406 407 408 409 410 411 412
  /* Paint back the resulting surface */
  if (needs_push_group)
    {
      cairo_pop_group_to_source (cr);
      cairo_paint (cr);
      cairo_restore (cr);
    }

413 414 415 416 417 418
  /* Inset shadows */
  _gtk_css_shadows_value_paint_box (box_shadow,
                                    cr,
                                    &bg.boxes[GTK_CSS_AREA_PADDING_BOX],
                                    TRUE);

419 420
  cairo_restore (cr);
}
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441

static gboolean
corner_value_is_right_angle (GtkCssValue *value)
{
  return _gtk_css_corner_value_get_x (value, 100) <= 0.0 &&
         _gtk_css_corner_value_get_y (value, 100) <= 0.0;
}

gboolean
gtk_css_style_render_background_is_opaque (GtkCssStyle *style)
{
  const GdkRGBA *color;

  color = _gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_COLOR));

  return color->alpha >= 1.0
      && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_LEFT_RADIUS))
      && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_RIGHT_RADIUS))
      && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_RIGHT_RADIUS))
      && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_LEFT_RADIUS));
}