]> Pileus Git - ~andy/gtk/blob - gtk/gtkborderimage.c
f13b0a83db1ac2c7ef4a61b3c7fd72c17e51cf7e
[~andy/gtk] / gtk / gtkborderimage.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, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <config.h>
25 #include <cairo-gobject.h>
26
27 #include <math.h>
28
29 #include "gtkborderimageprivate.h"
30 #include "gtkstylepropertiesprivate.h"
31 #include "gtkthemingengineprivate.h"
32
33 /* this is in case round() is not provided by the compiler, 
34  * such as in the case of C89 compilers, like MSVC
35  */
36 #include "fallback-c89.c"
37
38 G_DEFINE_BOXED_TYPE (GtkBorderImage, _gtk_border_image,
39                      _gtk_border_image_ref, _gtk_border_image_unref)
40
41 struct _GtkBorderImage {
42   GtkCssImage *source;
43
44   GtkBorder slice;
45   GtkBorder *width;
46   GtkCssBorderImageRepeat repeat;
47
48   gint ref_count;
49 };
50
51 GtkBorderImage *
52 _gtk_border_image_new (GtkCssImage             *source,
53                        GtkBorder               *slice,
54                        GtkBorder               *width,
55                        GtkCssBorderImageRepeat *repeat)
56 {
57   GtkBorderImage *image;
58
59   image = g_slice_new0 (GtkBorderImage);
60   image->ref_count = 1;
61
62   image->source = g_object_ref (source);
63
64   if (slice != NULL)
65     image->slice = *slice;
66
67   if (width != NULL)
68     image->width = gtk_border_copy (width);
69
70   if (repeat != NULL)
71     image->repeat = *repeat;
72
73   return image;
74 }
75
76 GtkBorderImage *
77 _gtk_border_image_new_for_engine (GtkThemingEngine *engine)
78 {
79   GtkCssImage *source;
80
81   source = g_value_get_object (_gtk_theming_engine_peek_property (engine, "border-image-source"));
82   if (source == NULL)
83     return NULL;
84
85   return _gtk_border_image_new (source,
86                                 g_value_get_boxed (_gtk_theming_engine_peek_property (engine, "border-image-slice")),
87                                 g_value_get_boxed (_gtk_theming_engine_peek_property (engine, "border-image-width")),
88                                 g_value_get_boxed (_gtk_theming_engine_peek_property (engine, "border-image-repeat")));
89 }
90
91 GtkBorderImage *
92 _gtk_border_image_ref (GtkBorderImage *image)
93 {
94   g_return_val_if_fail (image != NULL, NULL);
95
96   image->ref_count++;
97
98   return image;
99 }
100
101 void
102 _gtk_border_image_unref (GtkBorderImage *image)
103 {
104   g_return_if_fail (image != NULL);
105
106   image->ref_count--;
107
108   if (image->ref_count == 0)
109     {
110       g_object_unref (image->source);
111
112       if (image->width != NULL)
113         gtk_border_free (image->width);
114
115       g_slice_free (GtkBorderImage, image);
116     }
117 }
118
119 GParameter *
120 _gtk_border_image_unpack (const GValue *value,
121                           guint        *n_params)
122 {
123   GParameter *parameter = g_new0 (GParameter, 4);
124   GtkBorderImage *image = g_value_get_boxed (value);
125
126   parameter[0].name = "border-image-source";
127   g_value_init (&parameter[0].value, GTK_TYPE_CSS_IMAGE);
128
129   parameter[1].name = "border-image-slice";
130   g_value_init (&parameter[1].value, GTK_TYPE_BORDER);
131
132   parameter[2].name = "border-image-repeat";
133   g_value_init (&parameter[2].value, GTK_TYPE_CSS_BORDER_IMAGE_REPEAT);
134
135   parameter[3].name = "border-image-width";
136   g_value_init (&parameter[3].value, GTK_TYPE_BORDER);
137
138   if (image != NULL)
139     {
140       g_value_set_object (&parameter[0].value, image->source);
141       g_value_set_boxed (&parameter[1].value, &image->slice);
142       g_value_set_boxed (&parameter[2].value, &image->repeat);
143       g_value_set_boxed (&parameter[3].value, image->width);
144     }
145
146   *n_params = 4;
147   return parameter;
148 }
149
150 void
151 _gtk_border_image_pack (GValue             *value,
152                         GtkStyleProperties *props,
153                         GtkStateFlags       state)
154 {
155   GtkBorderImage *image;
156   GtkBorder *slice, *width;
157   GtkCssBorderImageRepeat *repeat;
158   GtkCssImage *source;
159   const GValue *val;
160
161   val = _gtk_style_properties_peek_property (props,
162                                              GTK_CSS_STYLE_PROPERTY (_gtk_style_property_lookup ("border-image-source")),
163                                              state);
164   source = g_value_get_object (val);
165   if (source == NULL)
166     return;
167
168   gtk_style_properties_get (props, state,
169                             "border-image-slice", &slice,
170                             "border-image-repeat", &repeat,
171                             "border-image-width", &width,
172                             NULL);
173
174   image = _gtk_border_image_new (source, slice, width, repeat);
175   g_value_take_boxed (value, image);
176
177   if (slice != NULL)
178     gtk_border_free (slice);
179
180   if (width != NULL)
181     gtk_border_free (width);
182
183   if (repeat != NULL)
184     g_free (repeat);
185 }
186
187 typedef struct _GtkBorderImageSliceSize GtkBorderImageSliceSize;
188 struct _GtkBorderImageSliceSize {
189   double offset;
190   double size;
191 };
192
193 static void
194 gtk_border_image_compute_border_size (GtkBorderImageSliceSize sizes[3],
195                                       double                  offset,
196                                       double                  area_size,
197                                       int                     start_border,
198                                       int                     end_border)
199 {
200   /* This code assumes area_size >= start_border + end_border */
201
202   sizes[0].offset = offset;
203   sizes[0].size = start_border;
204   sizes[1].offset = offset + start_border;
205   sizes[1].size = area_size - start_border - end_border;
206   sizes[2].offset = offset + area_size - end_border;
207   sizes[2].size = end_border;
208 }
209
210 static void
211 gtk_border_image_render_slice (cairo_t           *cr,
212                                cairo_surface_t   *slice,
213                                double             slice_width,
214                                double             slice_height,
215                                double             x,
216                                double             y,
217                                double             width,
218                                double             height,
219                                GtkCssBorderRepeatStyle  hrepeat,
220                                GtkCssBorderRepeatStyle  vrepeat)
221 {
222   double hscale, vscale;
223   double xstep, ystep;
224   cairo_extend_t extend = CAIRO_EXTEND_PAD;
225   cairo_matrix_t matrix;
226   cairo_pattern_t *pattern;
227
228   /* We can't draw center tiles yet */
229   g_assert (hrepeat == GTK_CSS_REPEAT_STYLE_STRETCH || vrepeat == GTK_CSS_REPEAT_STYLE_STRETCH);
230
231   hscale = width / slice_width;
232   vscale = height / slice_height;
233   xstep = width;
234   ystep = height;
235
236   switch (hrepeat)
237     {
238     case GTK_CSS_REPEAT_STYLE_REPEAT:
239       extend = CAIRO_EXTEND_REPEAT;
240       hscale = vscale;
241       break;
242     case GTK_CSS_REPEAT_STYLE_SPACE:
243       {
244         double space, n;
245
246         extend = CAIRO_EXTEND_NONE;
247         hscale = vscale;
248
249         xstep = hscale * slice_width;
250         n = floor (width / xstep);
251         space = (width - n * xstep) / (n + 1);
252         xstep += space;
253         x += space;
254         width -= 2 * space;
255       }
256       break;
257     case GTK_CSS_REPEAT_STYLE_STRETCH:
258       break;
259     case GTK_CSS_REPEAT_STYLE_ROUND:
260       extend = CAIRO_EXTEND_REPEAT;
261       hscale = width / (slice_width * MAX (round (width / (slice_width * vscale)), 1));
262       break;
263     default:
264       g_assert_not_reached ();
265       break;
266     }
267
268   switch (vrepeat)
269     {
270     case GTK_CSS_REPEAT_STYLE_REPEAT:
271       extend = CAIRO_EXTEND_REPEAT;
272       vscale = hscale;
273       break;
274     case GTK_CSS_REPEAT_STYLE_SPACE:
275       {
276         double space, n;
277
278         extend = CAIRO_EXTEND_NONE;
279         vscale = hscale;
280
281         ystep = vscale * slice_height;
282         n = floor (height / ystep);
283         space = (height - n * ystep) / (n + 1);
284         ystep += space;
285         y += space;
286         height -= 2 * space;
287       }
288       break;
289     case GTK_CSS_REPEAT_STYLE_STRETCH:
290       break;
291     case GTK_CSS_REPEAT_STYLE_ROUND:
292       extend = CAIRO_EXTEND_REPEAT;
293       vscale = height / (slice_height * MAX (round (height / (slice_height * hscale)), 1));
294       break;
295     default:
296       g_assert_not_reached ();
297       break;
298     }
299
300   pattern = cairo_pattern_create_for_surface (slice);
301
302   cairo_matrix_init_translate (&matrix,
303                                hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_width / 2 : 0,
304                                vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_height / 2 : 0);
305   cairo_matrix_scale (&matrix, 1 / hscale, 1 / vscale);
306   cairo_matrix_translate (&matrix,
307                           hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - width / 2 : 0,
308                           vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - height / 2 : 0);
309
310   cairo_pattern_set_matrix (pattern, &matrix);
311   cairo_pattern_set_extend (pattern, extend);
312
313   cairo_save (cr);
314   cairo_translate (cr, x, y);
315
316   for (y = 0; y < height; y += ystep)
317     {
318       for (x = 0; x < width; x += xstep)
319         {
320           cairo_save (cr);
321           cairo_translate (cr, x, y);
322           cairo_set_source (cr, pattern);
323           cairo_rectangle (cr, 0, 0, xstep, ystep);
324           cairo_fill (cr);
325           cairo_restore (cr);
326         }
327     }
328
329   cairo_restore (cr);
330
331   cairo_pattern_destroy (pattern);
332 }
333
334 static void
335 gtk_border_image_compute_slice_size (GtkBorderImageSliceSize sizes[3],
336                                      int                     surface_size,
337                                      int                     start_size,
338                                      int                     end_size)
339 {
340   sizes[0].size = MIN (start_size, surface_size);
341   sizes[0].offset = 0;
342
343   sizes[2].size = MIN (end_size, surface_size);
344   sizes[2].offset = surface_size - sizes[2].size;
345
346   sizes[1].size = MAX (0, surface_size - sizes[0].size - sizes[2].size);
347   sizes[1].offset = sizes[0].size;
348 }
349
350 void
351 _gtk_border_image_render (GtkBorderImage   *image,
352                           GtkBorder        *border_width,
353                           cairo_t          *cr,
354                           gdouble           x,
355                           gdouble           y,
356                           gdouble           width,
357                           gdouble           height)
358 {
359   cairo_surface_t *surface, *slice;
360   GtkBorderImageSliceSize vertical_slice[3], horizontal_slice[3];
361   GtkBorderImageSliceSize vertical_border[3], horizontal_border[3];
362   double source_width, source_height;
363   int h, v;
364
365   if (image->width != NULL)
366     border_width = image->width;
367
368   _gtk_css_image_get_concrete_size (image->source,
369                                     0, 0,
370                                     width, height,
371                                     &source_width, &source_height);
372
373   /* XXX: Optimize for (source_width == width && source_height == height) */
374
375   surface = _gtk_css_image_get_surface (image->source,
376                                         cairo_get_target (cr),
377                                         source_width, source_height);
378
379   gtk_border_image_compute_slice_size (horizontal_slice,
380                                        source_width, 
381                                        image->slice.left,
382                                        image->slice.right);
383   gtk_border_image_compute_slice_size (vertical_slice,
384                                        source_height, 
385                                        image->slice.top,
386                                        image->slice.bottom);
387   gtk_border_image_compute_border_size (horizontal_border,
388                                         x,
389                                         width,
390                                         border_width->left,
391                                         border_width->right);
392   gtk_border_image_compute_border_size (vertical_border,
393                                         y,
394                                         height,
395                                         border_width->top,
396                                         border_width->bottom);
397   
398   for (v = 0; v < 3; v++)
399     {
400       if (vertical_slice[v].size == 0 ||
401           vertical_border[v].size == 0)
402         continue;
403
404       for (h = 0; h < 3; h++)
405         {
406           if (horizontal_slice[h].size == 0 ||
407               horizontal_border[h].size == 0)
408             continue;
409
410           if (h == 1 && v == 1)
411             continue;
412
413           slice = cairo_surface_create_for_rectangle (surface,
414                                                       horizontal_slice[h].offset,
415                                                       vertical_slice[v].offset,
416                                                       horizontal_slice[h].size,
417                                                       vertical_slice[v].size);
418
419           gtk_border_image_render_slice (cr,
420                                          slice,
421                                          horizontal_slice[h].size,
422                                          vertical_slice[v].size,
423                                          horizontal_border[h].offset,
424                                          vertical_border[v].offset,
425                                          horizontal_border[h].size,
426                                          vertical_border[v].size,
427                                          h == 1 ? image->repeat.hrepeat : GTK_CSS_REPEAT_STYLE_STRETCH,
428                                          v == 1 ? image->repeat.vrepeat : GTK_CSS_REPEAT_STYLE_STRETCH);
429
430           cairo_surface_destroy (slice);
431         }
432     }
433
434   cairo_surface_destroy (surface);
435 }