1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
3 * Copyright (C) 2011 Red Hat, Inc.
4 *
5 * Authors: Carlos Garnacho <carlosg@gnome.org>
6 * Cosimo Cecchi <cosimoc@gnome.org>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
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 #include <math.h>
40
41 #include <gdk/gdk.h>
42
43 /* this is in case round() is not provided by the compiler,
44 * such as in the case of C89 compilers, like MSVC
45 */
46 #include "fallback-c89.c"
47
48 typedef struct _GtkThemingBackground GtkThemingBackground;
49
50 #define N_BOXES (3)
51
52 struct _GtkThemingBackground {
53 GtkCssStyle *style;
54
55 GtkRoundedBox boxes[N_BOXES];
56 };
57
58 static void
_gtk_theming_background_paint_color(GtkThemingBackground * bg,cairo_t * cr,const GdkRGBA * bg_color,GtkCssValue * background_image)59 _gtk_theming_background_paint_color (GtkThemingBackground *bg,
60 cairo_t *cr,
61 const GdkRGBA *bg_color,
62 GtkCssValue *background_image)
63 {
64 gint n_values = _gtk_css_array_value_get_n_values (background_image);
65 GtkCssArea clip = _gtk_css_area_value_get
66 (_gtk_css_array_value_get_nth
67 (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_CLIP),
68 n_values - 1));
69
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 static gboolean
_gtk_theming_background_needs_push_group(GtkCssStyle * style)76 _gtk_theming_background_needs_push_group (GtkCssStyle *style)
77 {
78 GtkCssValue *blend_modes;
79 gint i;
80
81 blend_modes = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE);
82
83 /*
84 * If we have any blend mode different than NORMAL, we'll need to
85 * push a group in order to correctly apply the blend modes.
86 */
87 for (i = _gtk_css_array_value_get_n_values (blend_modes); i > 0; i--)
88 {
89 GtkCssBlendMode blend_mode;
90
91 blend_mode = _gtk_css_blend_mode_value_get (_gtk_css_array_value_get_nth (blend_modes, i - 1));
92
93 if (blend_mode != GTK_CSS_BLEND_MODE_NORMAL)
94 return TRUE;
95 }
96
97 return FALSE;
98 }
99
100 static void
_gtk_theming_background_paint_layer(GtkThemingBackground * bg,guint idx,cairo_t * cr,GtkCssBlendMode blend_mode)101 _gtk_theming_background_paint_layer (GtkThemingBackground *bg,
102 guint idx,
103 cairo_t *cr,
104 GtkCssBlendMode blend_mode)
105 {
106 GtkCssRepeatStyle hrepeat, vrepeat;
107 const GtkCssValue *pos, *repeat;
108 GtkCssImage *image;
109 const GtkRoundedBox *origin;
110 double image_width, image_height;
111 double width, height;
112
113 pos = _gtk_css_array_value_get_nth (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_POSITION), idx);
114 repeat = _gtk_css_array_value_get_nth (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_REPEAT), idx);
115 hrepeat = _gtk_css_background_repeat_value_get_x (repeat);
116 vrepeat = _gtk_css_background_repeat_value_get_y (repeat);
117 image = _gtk_css_image_value_get_image (
118 _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 _gtk_css_area_value_get (
123 _gtk_css_array_value_get_nth (
124 gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_ORIGIN),
125 idx))];
126 width = origin->box.width;
127 height = origin->box.height;
128
129 if (image == NULL || width <= 0 || height <= 0)
130 return;
131
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 width,
135 height,
136 &image_width,
137 &image_height);
138
139 if (image_width <= 0 || image_height <= 0)
140 return;
141
142 /* optimization */
143 if (image_width == width)
144 hrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT;
145 if (image_height == height)
146 vrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT;
147
148
149 cairo_save (cr);
150
151 _gtk_rounded_box_path (
152 &bg->boxes[
153 _gtk_css_area_value_get (
154 _gtk_css_array_value_get_nth (
155 gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BACKGROUND_CLIP),
156 idx))],
157 cr);
158 cairo_clip (cr);
159
160
161 cairo_translate (cr, origin->box.x, origin->box.y);
162
163 /*
164 * Apply the blend mode, if any.
165 */
166 if (G_UNLIKELY (_gtk_css_blend_mode_get_operator (blend_mode) != cairo_get_operator (cr)))
167 cairo_set_operator (cr, _gtk_css_blend_mode_get_operator (blend_mode));
168
169
170 if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT && vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
171 {
172 cairo_translate (cr,
173 _gtk_css_position_value_get_x (pos, width - image_width),
174 _gtk_css_position_value_get_y (pos, height - image_height));
175 /* shortcut for normal case */
176 _gtk_css_image_draw (image, cr, image_width, image_height);
177 }
178 else
179 {
180 int surface_width, surface_height;
181 cairo_rectangle_t fill_rect;
182 cairo_surface_t *surface;
183 cairo_t *cr2;
184
185 /* If ‘background-repeat’ is ‘round’ for one (or both) dimensions,
186 * there is a second step. The UA must scale the image in that
187 * dimension (or both dimensions) so that it fits a whole number of
188 * times in the background positioning area. In the case of the width
189 * (height is analogous):
190 *
191 * If X ≠ 0 is the width of the image after step one and W is the width
192 * of the background positioning area, then the rounded width
193 * X' = W / round(W / X) where round() is a function that returns the
194 * nearest natural number (integer greater than zero).
195 *
196 * If ‘background-repeat’ is ‘round’ for one dimension only and if
197 * ‘background-size’ is ‘auto’ for the other dimension, then there is
198 * a third step: that other dimension is scaled so that the original
199 * aspect ratio is restored.
200 */
201 if (hrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
202 {
203 double n = round (width / image_width);
204
205 n = MAX (1, n);
206
207 if (vrepeat != GTK_CSS_REPEAT_STYLE_ROUND
208 /* && vsize == auto (it is by default) */)
209 image_height *= width / (image_width * n);
210 image_width = width / n;
211 }
212 if (vrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
213 {
214 double n = round (height / image_height);
215
216 n = MAX (1, n);
217
218 if (hrepeat != GTK_CSS_REPEAT_STYLE_ROUND
219 /* && hsize == auto (it is by default) */)
220 image_width *= height / (image_height * n);
221 image_height = height / n;
222 }
223
224 /* if hrepeat or vrepeat is 'space', we create a somewhat larger surface
225 * to store the extra space. */
226 if (hrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
227 {
228 double n = floor (width / image_width);
229 surface_width = n ? round (width / n) : 0;
230 }
231 else
232 surface_width = round (image_width);
233
234 if (vrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
235 {
236 double n = floor (height / image_height);
237 surface_height = n ? round (height / n) : 0;
238 }
239 else
240 surface_height = round (image_height);
241
242 surface = cairo_surface_create_similar (cairo_get_target (cr),
243 CAIRO_CONTENT_COLOR_ALPHA,
244 surface_width, surface_height);
245 cr2 = cairo_create (surface);
246 cairo_translate (cr2,
247 0.5 * (surface_width - image_width),
248 0.5 * (surface_height - image_height));
249 _gtk_css_image_draw (image, cr2, image_width, image_height);
250 cairo_destroy (cr2);
251
252 cairo_set_source_surface (cr, surface,
253 _gtk_css_position_value_get_x (pos, width - image_width),
254 _gtk_css_position_value_get_y (pos, height - image_height));
255 cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
256 cairo_surface_destroy (surface);
257
258 if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
259 {
260 fill_rect.x = _gtk_css_position_value_get_x (pos, width - image_width);
261 fill_rect.width = image_width;
262 }
263 else
264 {
265 fill_rect.x = 0;
266 fill_rect.width = width;
267 }
268
269 if (vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
270 {
271 fill_rect.y = _gtk_css_position_value_get_y (pos, height - image_height);
272 fill_rect.height = image_height;
273 }
274 else
275 {
276 fill_rect.y = 0;
277 fill_rect.height = height;
278 }
279
280 cairo_rectangle (cr, fill_rect.x, fill_rect.y,
281 fill_rect.width, fill_rect.height);
282 cairo_fill (cr);
283 }
284
285 /*
286 * Since this cairo_t can be shared with other widgets,
287 * we must reset the operator after all the backgrounds
288 * are properly rendered.
289 */
290 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
291
292 cairo_restore (cr);
293 }
294
295 static void
_gtk_theming_background_init_style(GtkThemingBackground * bg,double width,double height,GtkJunctionSides junction)296 _gtk_theming_background_init_style (GtkThemingBackground *bg,
297 double width,
298 double height,
299 GtkJunctionSides junction)
300 {
301 GtkBorder border, padding;
302
303 border.top = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_TOP_WIDTH), 100);
304 border.right = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_RIGHT_WIDTH), 100);
305 border.bottom = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_BOTTOM_WIDTH), 100);
306 border.left = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_BORDER_LEFT_WIDTH), 100);
307 padding.top = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_TOP), 100);
308 padding.right = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_RIGHT), 100);
309 padding.bottom = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_BOTTOM), 100);
310 padding.left = _gtk_css_number_value_get (gtk_css_style_get_value (bg->style, GTK_CSS_PROPERTY_PADDING_LEFT), 100);
311
312 /* In the CSS box model, by default the background positioning area is
313 * the padding-box, i.e. all the border-box minus the borders themselves,
314 * which determines also its default size, see
315 * http://dev.w3.org/csswg/css3-background/#background-origin
316 *
317 * In the future we might want to support different origins or clips, but
318 * right now we just shrink to the default.
319 */
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 bg->boxes[GTK_CSS_AREA_PADDING_BOX] = bg->boxes[GTK_CSS_AREA_BORDER_BOX];
324 _gtk_rounded_box_shrink (&bg->boxes[GTK_CSS_AREA_PADDING_BOX],
325 border.top, border.right,
326 border.bottom, border.left);
327
328 bg->boxes[GTK_CSS_AREA_CONTENT_BOX] = bg->boxes[GTK_CSS_AREA_PADDING_BOX];
329 _gtk_rounded_box_shrink (&bg->boxes[GTK_CSS_AREA_CONTENT_BOX],
330 padding.top, padding.right,
331 padding.bottom, padding.left);
332 }
333
334 void
gtk_css_style_render_background(GtkCssStyle * style,cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height,GtkJunctionSides junction)335 gtk_css_style_render_background (GtkCssStyle *style,
336 cairo_t *cr,
337 gdouble x,
338 gdouble y,
339 gdouble width,
340 gdouble height,
341 GtkJunctionSides junction)
342 {
343 GtkThemingBackground bg;
344 gint idx;
345 GtkCssValue *background_image;
346 GtkCssValue *blend_modes;
347 GtkCssValue *box_shadow;
348 const GdkRGBA *bg_color;
349 gboolean needs_push_group;
350 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 bg_color = _gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_COLOR));
355 box_shadow = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BOX_SHADOW);
356
357 /* This is the common default case of no background */
358 if (gtk_rgba_is_clear (bg_color) &&
359 _gtk_css_array_value_get_n_values (background_image) == 1 &&
360 _gtk_css_image_value_get_image (_gtk_css_array_value_get_nth (background_image, 0)) == NULL &&
361 _gtk_css_shadows_value_is_none (box_shadow))
362 return;
363
364 bg.style = style;
365 _gtk_theming_background_init_style (&bg, width, height, junction);
366
367 cairo_save (cr);
368 cairo_translate (cr, x, y);
369
370 /* Outset shadows */
371 _gtk_css_shadows_value_paint_box (box_shadow,
372 cr,
373 &bg.boxes[GTK_CSS_AREA_BORDER_BOX],
374 FALSE);
375
376 /*
377 * When we have a blend mode set for the background, we cannot blend the current
378 * widget's drawing with whatever the content that the Cairo context may have.
379 * Because of that, push the drawing to a new group before drawing the background
380 * layers, and paint the resulting image back after.
381 */
382 needs_push_group = _gtk_theming_background_needs_push_group (style);
383
384 if (needs_push_group)
385 {
386 cairo_save (cr);
387 cairo_rectangle (cr, 0, 0, width, height);
388 cairo_clip (cr);
389 cairo_push_group (cr);
390 }
391
392 _gtk_theming_background_paint_color (&bg, cr, bg_color, background_image);
393
394 number_of_layers = _gtk_css_array_value_get_n_values (background_image);
395
396 for (idx = number_of_layers - 1; idx >= 0; idx--)
397 {
398 GtkCssBlendMode blend_mode;
399
400 blend_mode = _gtk_css_blend_mode_value_get (_gtk_css_array_value_get_nth (blend_modes, idx));
401
402 _gtk_theming_background_paint_layer (&bg, idx, cr, blend_mode);
403 }
404
405 /* Paint back the resulting surface */
406 if (needs_push_group)
407 {
408 cairo_pop_group_to_source (cr);
409 cairo_paint (cr);
410 cairo_restore (cr);
411 }
412
413 /* Inset shadows */
414 _gtk_css_shadows_value_paint_box (box_shadow,
415 cr,
416 &bg.boxes[GTK_CSS_AREA_PADDING_BOX],
417 TRUE);
418
419 cairo_restore (cr);
420 }
421
422 static gboolean
corner_value_is_right_angle(GtkCssValue * value)423 corner_value_is_right_angle (GtkCssValue *value)
424 {
425 return _gtk_css_corner_value_get_x (value, 100) <= 0.0 &&
426 _gtk_css_corner_value_get_y (value, 100) <= 0.0;
427 }
428
429 gboolean
gtk_css_style_render_background_is_opaque(GtkCssStyle * style)430 gtk_css_style_render_background_is_opaque (GtkCssStyle *style)
431 {
432 const GdkRGBA *color;
433
434 color = _gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_COLOR));
435
436 return color->alpha >= 1.0
437 && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_LEFT_RADIUS))
438 && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_RIGHT_RADIUS))
439 && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_RIGHT_RADIUS))
440 && corner_value_is_right_angle (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_LEFT_RADIUS));
441 }
442