]> Pileus Git - ~andy/gtk/blob - gtk/gtkthemingbackground.c
themingbackground: Introduce gtk_theming_background_get_box()
[~andy/gtk] / gtk / gtkthemingbackground.c
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 "gtkthemingbackgroundprivate.h"
25
26 #include "gtkcssarrayvalueprivate.h"
27 #include "gtkcssbgsizevalueprivate.h"
28 #include "gtkcssenumvalueprivate.h"
29 #include "gtkcssimagevalueprivate.h"
30 #include "gtkcssshadowsvalueprivate.h"
31 #include "gtkcsspositionvalueprivate.h"
32 #include "gtkcssrepeatvalueprivate.h"
33 #include "gtkcsstypesprivate.h"
34 #include "gtkthemingengineprivate.h"
35
36 #include <math.h>
37
38 #include <gdk/gdk.h>
39
40 /* this is in case round() is not provided by the compiler, 
41  * such as in the case of C89 compilers, like MSVC
42  */
43 #include "fallback-c89.c"
44
45 typedef struct {
46   cairo_rectangle_t image_rect;
47
48   gint idx;
49 } GtkThemingBackgroundLayer;
50
51 static void
52 _gtk_theming_background_layer_apply_origin (GtkThemingBackground *bg,
53                                             GtkThemingBackgroundLayer *layer)
54 {
55   cairo_rectangle_t image_rect;
56   GtkCssValue *value = _gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_ORIGIN);
57   GtkCssArea origin = _gtk_css_area_value_get (_gtk_css_array_value_get_nth (value, layer->idx));
58
59   /* The default size of the background image depends on the
60      background-origin value as this affects the top left
61      and the bottom right corners. */
62   switch (origin) {
63   case GTK_CSS_AREA_BORDER_BOX:
64     image_rect.x = 0;
65     image_rect.y = 0;
66     image_rect.width = bg->paint_area.width;
67     image_rect.height = bg->paint_area.height;
68     break;
69   case GTK_CSS_AREA_CONTENT_BOX:
70     image_rect.x = bg->border.left + bg->padding.left;
71     image_rect.y = bg->border.top + bg->padding.top;
72     image_rect.width = bg->paint_area.width - bg->border.left - bg->border.right - bg->padding.left - bg->padding.right;
73     image_rect.height = bg->paint_area.height - bg->border.top - bg->border.bottom - bg->padding.top - bg->padding.bottom;
74     break;
75   case GTK_CSS_AREA_PADDING_BOX:
76   default:
77     image_rect.x = bg->border.left;
78     image_rect.y = bg->border.top;
79     image_rect.width = bg->paint_area.width - bg->border.left - bg->border.right;
80     image_rect.height = bg->paint_area.height - bg->border.top - bg->border.bottom;
81     break;
82   }
83
84   /* XXX: image_rect might have negative width/height here.
85    * Do we need to do something about it? */
86   layer->image_rect = image_rect;
87 }
88
89 static const GtkRoundedBox *
90 gtk_theming_background_get_box (GtkThemingBackground *bg,
91                                 GtkCssArea            area)
92 {
93   switch (area)
94     {
95     case GTK_CSS_AREA_BORDER_BOX:
96       return &bg->border_box;
97     case GTK_CSS_AREA_PADDING_BOX:
98       return &bg->padding_box;
99     case GTK_CSS_AREA_CONTENT_BOX:
100       return &bg->content_box;
101     default:
102       g_return_val_if_reached (&bg->border_box);
103   }
104 }
105
106 static void
107 _gtk_theming_background_paint_color (GtkThemingBackground *bg,
108                                      cairo_t              *cr,
109                                      GtkCssValue          *background_image)
110 {
111   gint n_values = _gtk_css_array_value_get_n_values (background_image);
112   GtkCssArea clip = _gtk_css_area_value_get 
113     (_gtk_css_array_value_get_nth 
114      (_gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_CLIP), 
115       n_values - 1));
116
117   cairo_save (cr);
118   _gtk_rounded_box_path (gtk_theming_background_get_box (bg, clip), cr);
119   cairo_clip (cr);
120
121   gdk_cairo_set_source_rgba (cr, &bg->bg_color);
122   cairo_paint (cr);
123
124   cairo_restore (cr);
125 }
126
127 static void
128 _gtk_theming_background_paint_layer (GtkThemingBackground *bg,
129                                      GtkThemingBackgroundLayer *layer,
130                                      cairo_t              *cr)
131 {
132   GtkCssRepeatStyle hrepeat, vrepeat;
133   const GtkCssValue *pos, *repeat;
134   GtkCssImage *image;
135   double image_width, image_height;
136   double width, height;
137
138   pos = _gtk_css_array_value_get_nth (_gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_POSITION), layer->idx);
139   repeat = _gtk_css_array_value_get_nth (_gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_REPEAT), layer->idx);
140   hrepeat = _gtk_css_background_repeat_value_get_x (repeat);
141   vrepeat = _gtk_css_background_repeat_value_get_y (repeat);
142   image = _gtk_css_image_value_get_image (
143               _gtk_css_array_value_get_nth (
144                   _gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_IMAGE),
145                   layer->idx));
146   width = layer->image_rect.width;
147   height = layer->image_rect.height;
148
149   if (image == NULL || width <= 0 || height <= 0)
150     return;
151
152   _gtk_css_bg_size_value_compute_size (_gtk_css_array_value_get_nth (_gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_SIZE), layer->idx),
153                                        image,
154                                        width,
155                                        height,
156                                        &image_width,
157                                        &image_height);
158
159   if (image_width <= 0 || image_height <= 0)
160     return;
161
162   /* optimization */
163   if (image_width == width)
164     hrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT;
165   if (image_height == height)
166     vrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT;
167
168
169   cairo_save (cr);
170
171   _gtk_rounded_box_path (
172       gtk_theming_background_get_box (
173           bg,
174           _gtk_css_area_value_get (
175               _gtk_css_array_value_get_nth (
176                   _gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_CLIP),
177                   layer->idx))),
178       cr);
179   cairo_clip (cr);
180
181
182   cairo_translate (cr, layer->image_rect.x, layer->image_rect.y);
183
184   if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT && vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
185     {
186       cairo_translate (cr,
187                        _gtk_css_position_value_get_x (pos, width - image_width),
188                        _gtk_css_position_value_get_y (pos, height - image_height));
189       /* shortcut for normal case */
190       _gtk_css_image_draw (image, cr, image_width, image_height);
191     }
192   else
193     {
194       int surface_width, surface_height;
195       cairo_rectangle_t fill_rect;
196       cairo_surface_t *surface;
197       cairo_t *cr2;
198
199       /* If ‘background-repeat’ is ‘round’ for one (or both) dimensions,
200        * there is a second step. The UA must scale the image in that
201        * dimension (or both dimensions) so that it fits a whole number of
202        * times in the background positioning area. In the case of the width
203        * (height is analogous):
204        *
205        * If X ≠ 0 is the width of the image after step one and W is the width
206        * of the background positioning area, then the rounded width
207        * X' = W / round(W / X) where round() is a function that returns the
208        * nearest natural number (integer greater than zero). 
209        *
210        * If ‘background-repeat’ is ‘round’ for one dimension only and if
211        * ‘background-size’ is ‘auto’ for the other dimension, then there is
212        * a third step: that other dimension is scaled so that the original
213        * aspect ratio is restored. 
214        */
215       if (hrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
216         {
217           double n = round (width / image_width);
218
219           n = MAX (1, n);
220
221           if (vrepeat != GTK_CSS_REPEAT_STYLE_ROUND
222               /* && vsize == auto (it is by default) */)
223             image_height *= width / (image_width * n);
224           image_width = width / n;
225         }
226       if (vrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
227         {
228           double n = round (height / image_height);
229
230           n = MAX (1, n);
231
232           if (hrepeat != GTK_CSS_REPEAT_STYLE_ROUND
233               /* && hsize == auto (it is by default) */)
234             image_width *= height / (image_height * n);
235           image_height = height / n;
236         }
237
238       /* if hrepeat or vrepeat is 'space', we create a somewhat larger surface
239        * to store the extra space. */
240       if (hrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
241         {
242           double n = floor (width / image_width);
243           surface_width = n ? round (width / n) : 0;
244         }
245       else
246         surface_width = round (image_width);
247
248       if (vrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
249         {
250           double n = floor (height / image_height);
251           surface_height = n ? round (height / n) : 0;
252         }
253       else
254         surface_height = round (image_height);
255
256       surface = cairo_surface_create_similar (cairo_get_target (cr),
257                                               CAIRO_CONTENT_COLOR_ALPHA,
258                                               surface_width, surface_height);
259       cr2 = cairo_create (surface);
260       cairo_translate (cr2,
261                        0.5 * (surface_width - image_width),
262                        0.5 * (surface_height - image_height));
263       _gtk_css_image_draw (image, cr2, image_width, image_height);
264       cairo_destroy (cr2);
265
266       cairo_set_source_surface (cr, surface,
267                                 _gtk_css_position_value_get_x (pos, width - image_width),
268                                 _gtk_css_position_value_get_y (pos, height - image_height));
269       cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
270       cairo_surface_destroy (surface);
271
272       if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
273         {
274           fill_rect.x = _gtk_css_position_value_get_x (pos, width - image_width);
275           fill_rect.width = image_width;
276         }
277       else
278         {
279           fill_rect.x = 0;
280           fill_rect.width = width;
281         }
282
283       if (vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT)
284         {
285           fill_rect.y = _gtk_css_position_value_get_y (pos, height - image_height);
286           fill_rect.height = image_height;
287         }
288       else
289         {
290           fill_rect.y = 0;
291           fill_rect.height = height;
292         }
293
294       cairo_rectangle (cr, fill_rect.x, fill_rect.y,
295                        fill_rect.width, fill_rect.height);
296       cairo_fill (cr);
297     }
298
299
300   cairo_restore (cr);
301 }
302
303 static void
304 _gtk_theming_background_apply_shadow (GtkThemingBackground *bg,
305                                       cairo_t              *cr)
306 {
307   _gtk_css_shadows_value_paint_box (_gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BOX_SHADOW),
308                                     cr,
309                                     &bg->padding_box);
310 }
311
312 static void
313 _gtk_theming_background_init_layer (GtkThemingBackground *bg,
314                                     GtkThemingBackgroundLayer *layer,
315                                     gint idx)
316 {
317   layer->idx = idx;
318
319   _gtk_theming_background_layer_apply_origin (bg, layer);
320 }
321
322 static void
323 _gtk_theming_background_init_context (GtkThemingBackground *bg)
324 {
325   GtkStateFlags flags = gtk_style_context_get_state (bg->context);
326
327   gtk_style_context_get_border (bg->context, flags, &bg->border);
328   gtk_style_context_get_padding (bg->context, flags, &bg->padding);
329   gtk_style_context_get_background_color (bg->context, flags, &bg->bg_color);
330
331   /* In the CSS box model, by default the background positioning area is
332    * the padding-box, i.e. all the border-box minus the borders themselves,
333    * which determines also its default size, see
334    * http://dev.w3.org/csswg/css3-background/#background-origin
335    *
336    * In the future we might want to support different origins or clips, but
337    * right now we just shrink to the default.
338    */
339   _gtk_rounded_box_init_rect (&bg->border_box, 0, 0, bg->paint_area.width, bg->paint_area.height);
340   _gtk_rounded_box_apply_border_radius_for_context (&bg->border_box, bg->context, bg->junction);
341
342   bg->padding_box = bg->border_box;
343   _gtk_rounded_box_shrink (&bg->padding_box,
344                            bg->border.top, bg->border.right,
345                            bg->border.bottom, bg->border.left);
346
347   bg->content_box = bg->padding_box;
348   _gtk_rounded_box_shrink (&bg->content_box,
349                            bg->padding.top, bg->padding.right,
350                            bg->padding.bottom, bg->padding.left);
351 }
352
353 void
354 _gtk_theming_background_init (GtkThemingBackground *bg,
355                               GtkThemingEngine     *engine,
356                               gdouble               x,
357                               gdouble               y,
358                               gdouble               width,
359                               gdouble               height,
360                               GtkJunctionSides      junction)
361 {
362   GtkStyleContext *context;
363
364   g_assert (bg != NULL);
365
366   context = _gtk_theming_engine_get_context (engine);
367   _gtk_theming_background_init_from_context (bg, context,
368                                              x, y, width, height,
369                                              junction);
370 }
371
372 void
373 _gtk_theming_background_init_from_context (GtkThemingBackground *bg,
374                                            GtkStyleContext      *context,
375                                            gdouble               x,
376                                            gdouble               y,
377                                            gdouble               width,
378                                            gdouble               height,
379                                            GtkJunctionSides      junction)
380 {
381   g_assert (bg != NULL);
382
383   bg->context = context;
384
385   bg->paint_area.x = x;
386   bg->paint_area.y = y;
387   bg->paint_area.width = width;
388   bg->paint_area.height = height;
389
390   bg->junction = junction;
391
392   _gtk_theming_background_init_context (bg);
393 }
394
395 void
396 _gtk_theming_background_render (GtkThemingBackground *bg,
397                                 cairo_t              *cr)
398 {
399   gint idx;
400   GtkThemingBackgroundLayer layer;
401   GtkCssValue *background_image;
402
403   background_image = _gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_IMAGE);
404
405   cairo_save (cr);
406   cairo_translate (cr, bg->paint_area.x, bg->paint_area.y);
407
408   _gtk_theming_background_paint_color (bg, cr, background_image);
409
410   for (idx = _gtk_css_array_value_get_n_values (background_image) - 1; idx >= 0; idx--)
411     {
412       _gtk_theming_background_init_layer (bg, &layer, idx);
413       _gtk_theming_background_paint_layer (bg, &layer, cr);
414     }
415
416   _gtk_theming_background_apply_shadow (bg, cr);
417
418   cairo_restore (cr);
419 }
420
421 gboolean
422 _gtk_theming_background_has_background_image (GtkThemingBackground *bg)
423 {
424   GtkCssImage *image;
425   GtkCssValue *value = _gtk_style_context_peek_property (bg->context, GTK_CSS_PROPERTY_BACKGROUND_IMAGE);
426
427   if (_gtk_css_array_value_get_n_values (value) == 0)
428     return FALSE;
429
430   image = _gtk_css_image_value_get_image (_gtk_css_array_value_get_nth (value, 0));
431   return (image != NULL);
432 }