]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimage.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcssimage.c
1 /*
2  * Copyright © 2011 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19
20 #include "config.h"
21
22 #include "gtkcssimageprivate.h"
23
24 #include "gtkcsscomputedvaluesprivate.h"
25
26 /* for the types only */
27 #include "gtk/gtkcssimagecrossfadeprivate.h"
28 #include "gtk/gtkcssimagegradientprivate.h"
29 #include "gtk/gtkcssimagelinearprivate.h"
30 #include "gtk/gtkcssimageurlprivate.h"
31 #include "gtk/gtkcssimagewin32private.h"
32
33 G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT)
34
35 static int
36 gtk_css_image_real_get_width (GtkCssImage *image)
37 {
38   return 0;
39 }
40
41 static int
42 gtk_css_image_real_get_height (GtkCssImage *image)
43 {
44   return 0;
45 }
46
47 static double
48 gtk_css_image_real_get_aspect_ratio (GtkCssImage *image)
49 {
50   int width, height;
51
52   width = _gtk_css_image_get_width (image);
53   height = _gtk_css_image_get_height (image);
54
55   if (width && height)
56     return (double) width / height;
57   else
58     return 0;
59 }
60
61 static GtkCssImage *
62 gtk_css_image_real_compute (GtkCssImage             *image,
63                             guint                    property_id,
64                             GtkStyleProviderPrivate *provider,
65                             GtkCssComputedValues    *values,
66                             GtkCssComputedValues    *parent_values,
67                             GtkCssDependencies      *dependencies)
68 {
69   return g_object_ref (image);
70 }
71
72 static gboolean
73 gtk_css_image_real_equal (GtkCssImage *image1,
74                           GtkCssImage *image2)
75 {
76   return FALSE;
77 }
78
79 static GtkCssImage *
80 gtk_css_image_real_transition (GtkCssImage *start,
81                                GtkCssImage *end,
82                                guint        property_id,
83                                double       progress)
84 {
85   if (progress <= 0.0)
86     return g_object_ref (start);
87   else if (progress >= 1.0)
88     return end ? g_object_ref (end) : NULL;
89   else
90     return _gtk_css_image_cross_fade_new (start, end, progress);
91 }
92
93 static void
94 _gtk_css_image_class_init (GtkCssImageClass *klass)
95 {
96   klass->get_width = gtk_css_image_real_get_width;
97   klass->get_height = gtk_css_image_real_get_height;
98   klass->get_aspect_ratio = gtk_css_image_real_get_aspect_ratio;
99   klass->compute = gtk_css_image_real_compute;
100   klass->equal = gtk_css_image_real_equal;
101   klass->transition = gtk_css_image_real_transition;
102 }
103
104 static void
105 _gtk_css_image_init (GtkCssImage *image)
106 {
107 }
108
109 int
110 _gtk_css_image_get_width (GtkCssImage *image)
111 {
112   GtkCssImageClass *klass;
113
114   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
115
116   klass = GTK_CSS_IMAGE_GET_CLASS (image);
117
118   return klass->get_width (image);
119 }
120
121 int
122 _gtk_css_image_get_height (GtkCssImage *image)
123 {
124   GtkCssImageClass *klass;
125
126   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
127
128   klass = GTK_CSS_IMAGE_GET_CLASS (image);
129
130   return klass->get_height (image);
131 }
132
133 double
134 _gtk_css_image_get_aspect_ratio (GtkCssImage *image)
135 {
136   GtkCssImageClass *klass;
137
138   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
139
140   klass = GTK_CSS_IMAGE_GET_CLASS (image);
141
142   return klass->get_aspect_ratio (image);
143 }
144
145 GtkCssImage *
146 _gtk_css_image_compute (GtkCssImage             *image,
147                         guint                    property_id,
148                         GtkStyleProviderPrivate *provider,
149                         GtkCssComputedValues    *values,
150                         GtkCssComputedValues    *parent_values,
151                         GtkCssDependencies      *dependencies)
152 {
153   GtkCssDependencies unused;
154   GtkCssImageClass *klass;
155
156   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
157   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
158   g_return_val_if_fail (parent_values == NULL || GTK_IS_CSS_COMPUTED_VALUES (parent_values), NULL);
159
160   if (dependencies == NULL)
161     dependencies = &unused;
162   *dependencies = 0;
163
164   klass = GTK_CSS_IMAGE_GET_CLASS (image);
165
166   return klass->compute (image, property_id, provider, values, parent_values, dependencies);
167 }
168
169 GtkCssImage *
170 _gtk_css_image_transition (GtkCssImage *start,
171                            GtkCssImage *end,
172                            guint        property_id,
173                            double       progress)
174 {
175   GtkCssImageClass *klass;
176
177   g_return_val_if_fail (start == NULL || GTK_IS_CSS_IMAGE (start), NULL);
178   g_return_val_if_fail (end == NULL || GTK_IS_CSS_IMAGE (end), NULL);
179
180   progress = CLAMP (progress, 0.0, 1.0);
181
182   if (start == NULL)
183     {
184       if (end == NULL)
185         return NULL;
186       else
187         {
188           start = end;
189           end = NULL;
190           progress = 1.0 - progress;
191         }
192     }
193
194   klass = GTK_CSS_IMAGE_GET_CLASS (start);
195
196   return klass->transition (start, end, property_id, progress);
197 }
198
199 gboolean
200 _gtk_css_image_equal (GtkCssImage *image1,
201                       GtkCssImage *image2)
202 {
203   GtkCssImageClass *klass;
204
205   g_return_val_if_fail (image1 == NULL || GTK_IS_CSS_IMAGE (image1), FALSE);
206   g_return_val_if_fail (image2 == NULL || GTK_IS_CSS_IMAGE (image2), FALSE);
207
208   if (image1 == image2)
209     return TRUE;
210
211   if (image1 == NULL || image2 == NULL)
212     return FALSE;
213
214   if (G_OBJECT_TYPE (image1) != G_OBJECT_TYPE (image2))
215     return FALSE;
216
217   klass = GTK_CSS_IMAGE_GET_CLASS (image1);
218
219   return klass->equal (image1, image2);
220 }
221
222 void
223 _gtk_css_image_draw (GtkCssImage        *image,
224                      cairo_t            *cr,
225                      double              width,
226                      double              height)
227 {
228   GtkCssImageClass *klass;
229
230   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
231   g_return_if_fail (cr != NULL);
232   g_return_if_fail (width > 0);
233   g_return_if_fail (height > 0);
234
235   cairo_save (cr);
236
237   klass = GTK_CSS_IMAGE_GET_CLASS (image);
238
239   klass->draw (image, cr, width, height);
240
241   cairo_restore (cr);
242 }
243
244 void
245 _gtk_css_image_print (GtkCssImage *image,
246                       GString     *string)
247 {
248   GtkCssImageClass *klass;
249
250   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
251   g_return_if_fail (string != NULL);
252
253   klass = GTK_CSS_IMAGE_GET_CLASS (image);
254
255   klass->print (image, string);
256 }
257
258 /* Applies the algorithm outlined in
259  * http://dev.w3.org/csswg/css3-images/#default-sizing
260  */
261 void
262 _gtk_css_image_get_concrete_size (GtkCssImage *image,
263                                   double       specified_width,
264                                   double       specified_height,
265                                   double       default_width,
266                                   double       default_height,
267                                   double      *concrete_width,
268                                   double      *concrete_height)
269 {
270   double image_width, image_height, image_aspect;
271
272   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
273   g_return_if_fail (specified_width >= 0);
274   g_return_if_fail (specified_height >= 0);
275   g_return_if_fail (default_width > 0);
276   g_return_if_fail (default_height > 0);
277   g_return_if_fail (concrete_width != NULL);
278   g_return_if_fail (concrete_height != NULL);
279
280   /* If the specified size is a definite width and height,
281    * the concrete object size is given that width and height.
282    */
283   if (specified_width && specified_height)
284     {
285       *concrete_width = specified_width;
286       *concrete_height = specified_height;
287       return;
288     }
289
290   image_width  = _gtk_css_image_get_width (image);
291   image_height = _gtk_css_image_get_height (image);
292   image_aspect = _gtk_css_image_get_aspect_ratio (image);
293
294   /* If the specified size has neither a definite width nor height,
295    * and has no additional contraints, the dimensions of the concrete
296    * object size are calculated as follows:
297    */
298   if (specified_width == 0.0 && specified_height == 0.0)
299     {
300       /* If the object has only an intrinsic aspect ratio,
301        * the concrete object size must have that aspect ratio,
302        * and additionally be as large as possible without either
303        * its height or width exceeding the height or width of the
304        * default object size.
305        */
306       if (image_aspect > 0 && image_width == 0 && image_height == 0)
307         {
308           if (image_aspect * default_height > default_width)
309             {
310               *concrete_width = default_height * image_aspect;
311               *concrete_height = default_height;
312             }
313           else
314             {
315               *concrete_width = default_width;
316               *concrete_height = default_width / image_aspect;
317             }
318         }
319
320       /* Otherwise, the width and height of the concrete object
321        * size is the same as the object's intrinsic width and
322        * intrinsic height, if they exist.
323        * If the concrete object size is still missing a width or
324        * height, and the object has an intrinsic aspect ratio,
325        * the missing dimension is calculated from the present
326        * dimension and the intrinsic aspect ratio.
327        * Otherwise, the missing dimension is taken from the default
328        * object size. 
329        */
330       if (image_width)
331         *concrete_width = image_width;
332       else if (image_aspect)
333         *concrete_width = image_height * image_aspect;
334       else
335         *concrete_width = default_width;
336
337       if (image_height)
338         *concrete_height = image_height;
339       else if (image_aspect)
340         *concrete_height = image_width / image_aspect;
341       else
342         *concrete_height = default_height;
343
344       return;
345     }
346
347   /* If the specified size has only a width or height, but not both,
348    * then the concrete object size is given that specified width or height.
349    * The other dimension is calculated as follows:
350    * If the object has an intrinsic aspect ratio, the missing dimension of
351    * the concrete object size is calculated using the intrinsic aspect-ratio
352    * and the present dimension.
353    * Otherwise, if the missing dimension is present in the object's intrinsic
354    * dimensions, the missing dimension is taken from the object's intrinsic
355    * dimensions.
356    * Otherwise, the missing dimension of the concrete object size is taken
357    * from the default object size. 
358    */
359   if (specified_width)
360     {
361       *concrete_width = specified_width;
362       if (image_aspect)
363         *concrete_height = specified_width / image_aspect;
364       else if (image_height)
365         *concrete_height = image_height;
366       else
367         *concrete_height = default_height;
368     }
369   else
370     {
371       *concrete_height = specified_height;
372       if (image_aspect)
373         *concrete_width = specified_height * image_aspect;
374       else if (image_width)
375         *concrete_width = image_width;
376       else
377         *concrete_width = default_width;
378     }
379 }
380
381 cairo_surface_t *
382 _gtk_css_image_get_surface (GtkCssImage     *image,
383                             cairo_surface_t *target,
384                             int              surface_width,
385                             int              surface_height)
386 {
387   cairo_surface_t *result;
388   cairo_t *cr;
389
390   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
391   g_return_val_if_fail (surface_width > 0, NULL);
392   g_return_val_if_fail (surface_height > 0, NULL);
393
394   if (target)
395     result = cairo_surface_create_similar (target,
396                                            CAIRO_CONTENT_COLOR_ALPHA,
397                                            surface_width,
398                                            surface_height);
399   else
400     result = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
401                                          surface_width,
402                                          surface_height);
403
404   cr = cairo_create (result);
405   _gtk_css_image_draw (image, cr, surface_width, surface_height);
406   cairo_destroy (cr);
407
408   return result;
409 }
410
411 static GType
412 gtk_css_image_get_parser_type (GtkCssParser *parser)
413 {
414   static const struct {
415     const char *prefix;
416     GType (* type_func) (void);
417   } image_types[] = {
418     { "url", _gtk_css_image_url_get_type },
419     { "-gtk-gradient", _gtk_css_image_gradient_get_type },
420     { "-gtk-win32-theme-part", _gtk_css_image_win32_get_type },
421     { "linear-gradient", _gtk_css_image_linear_get_type },
422     { "repeating-linear-gradient", _gtk_css_image_linear_get_type },
423     { "cross-fade", _gtk_css_image_cross_fade_get_type }
424   };
425   guint i;
426
427   for (i = 0; i < G_N_ELEMENTS (image_types); i++)
428     {
429       if (_gtk_css_parser_has_prefix (parser, image_types[i].prefix))
430         return image_types[i].type_func ();
431     }
432
433   return G_TYPE_INVALID;
434 }
435
436 /**
437  * _gtk_css_image_can_parse:
438  * @parser: a css parser
439  *
440  * Checks if the parser can potentially parse the given stream as an
441  * image from looking at the first token of @parser. This is useful for
442  * implementing shorthand properties. A successful parse of an image
443  * can not be guaranteed.
444  *
445  * Returns: %TURE if it looks like an image.
446  **/
447 gboolean
448 _gtk_css_image_can_parse (GtkCssParser *parser)
449 {
450   return gtk_css_image_get_parser_type (parser) != G_TYPE_INVALID;
451 }
452
453 GtkCssImage *
454 _gtk_css_image_new_parse (GtkCssParser *parser)
455 {
456   GtkCssImageClass *klass;
457   GtkCssImage *image;
458   GType image_type;
459
460   g_return_val_if_fail (parser != NULL, NULL);
461
462   image_type = gtk_css_image_get_parser_type (parser);
463   if (image_type == G_TYPE_INVALID)
464     {
465       _gtk_css_parser_error (parser, "Not a valid image");
466       return NULL;
467     }
468
469   image = g_object_new (image_type, NULL);
470
471   klass = GTK_CSS_IMAGE_GET_CLASS (image);
472   if (!klass->parse (image, parser))
473     {
474       g_object_unref (image);
475       return NULL;
476     }
477
478   return image;
479 }
480