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