]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimage.c
cssimage: Make image transition a vfunc
[~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 GtkCssImage *
73 gtk_css_image_real_transition (GtkCssImage *start,
74                                GtkCssImage *end,
75                                guint        property_id,
76                                double       progress)
77 {
78   return _gtk_css_image_cross_fade_new (start, end, progress);
79 }
80
81 static void
82 _gtk_css_image_class_init (GtkCssImageClass *klass)
83 {
84   klass->get_width = gtk_css_image_real_get_width;
85   klass->get_height = gtk_css_image_real_get_height;
86   klass->get_aspect_ratio = gtk_css_image_real_get_aspect_ratio;
87   klass->compute = gtk_css_image_real_compute;
88   klass->transition = gtk_css_image_real_transition;
89 }
90
91 static void
92 _gtk_css_image_init (GtkCssImage *image)
93 {
94 }
95
96 int
97 _gtk_css_image_get_width (GtkCssImage *image)
98 {
99   GtkCssImageClass *klass;
100
101   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
102
103   klass = GTK_CSS_IMAGE_GET_CLASS (image);
104
105   return klass->get_width (image);
106 }
107
108 int
109 _gtk_css_image_get_height (GtkCssImage *image)
110 {
111   GtkCssImageClass *klass;
112
113   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
114
115   klass = GTK_CSS_IMAGE_GET_CLASS (image);
116
117   return klass->get_height (image);
118 }
119
120 double
121 _gtk_css_image_get_aspect_ratio (GtkCssImage *image)
122 {
123   GtkCssImageClass *klass;
124
125   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
126
127   klass = GTK_CSS_IMAGE_GET_CLASS (image);
128
129   return klass->get_aspect_ratio (image);
130 }
131
132 GtkCssImage *
133 _gtk_css_image_compute (GtkCssImage             *image,
134                         guint                    property_id,
135                         GtkStyleProviderPrivate *provider,
136                         GtkCssComputedValues    *values,
137                         GtkCssComputedValues    *parent_values,
138                         GtkCssDependencies      *dependencies)
139 {
140   GtkCssDependencies unused;
141   GtkCssImageClass *klass;
142
143   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
144   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
145   g_return_val_if_fail (parent_values == NULL || GTK_IS_CSS_COMPUTED_VALUES (parent_values), NULL);
146
147   if (dependencies == NULL)
148     dependencies = &unused;
149   *dependencies = 0;
150
151   klass = GTK_CSS_IMAGE_GET_CLASS (image);
152
153   return klass->compute (image, property_id, provider, values, parent_values, dependencies);
154 }
155
156 GtkCssImage *
157 _gtk_css_image_transition (GtkCssImage *start,
158                            GtkCssImage *end,
159                            guint        property_id,
160                            double       progress)
161 {
162   GtkCssImageClass *klass;
163
164   g_return_val_if_fail (start == NULL || GTK_IS_CSS_IMAGE (start), NULL);
165   g_return_val_if_fail (end == NULL || GTK_IS_CSS_IMAGE (end), NULL);
166
167   progress = CLAMP (progress, 0.0, 1.0);
168
169   if (start == NULL)
170     {
171       if (end == NULL)
172         return NULL;
173       else
174         {
175           start = end;
176           end = NULL;
177           progress = 1.0 - progress;
178         }
179     }
180
181   klass = GTK_CSS_IMAGE_GET_CLASS (start);
182
183   return klass->transition (start, end, property_id, progress);
184 }
185
186 void
187 _gtk_css_image_draw (GtkCssImage        *image,
188                      cairo_t            *cr,
189                      double              width,
190                      double              height)
191 {
192   GtkCssImageClass *klass;
193
194   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
195   g_return_if_fail (cr != NULL);
196
197   cairo_save (cr);
198
199   klass = GTK_CSS_IMAGE_GET_CLASS (image);
200
201   klass->draw (image, cr, width, height);
202
203   cairo_restore (cr);
204 }
205
206 void
207 _gtk_css_image_print (GtkCssImage *image,
208                       GString     *string)
209 {
210   GtkCssImageClass *klass;
211
212   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
213   g_return_if_fail (string != NULL);
214
215   klass = GTK_CSS_IMAGE_GET_CLASS (image);
216
217   klass->print (image, string);
218 }
219
220 /* Applies the algorithm outlined in
221  * http://dev.w3.org/csswg/css3-images/#default-sizing
222  */
223 void
224 _gtk_css_image_get_concrete_size (GtkCssImage *image,
225                                   double       specified_width,
226                                   double       specified_height,
227                                   double       default_width,
228                                   double       default_height,
229                                   double      *concrete_width,
230                                   double      *concrete_height)
231 {
232   double image_width, image_height, image_aspect;
233
234   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
235   g_return_if_fail (specified_width >= 0);
236   g_return_if_fail (specified_height >= 0);
237   g_return_if_fail (default_width > 0);
238   g_return_if_fail (default_height > 0);
239   g_return_if_fail (concrete_width != NULL);
240   g_return_if_fail (concrete_height != NULL);
241
242   /* If the specified size is a definite width and height,
243    * the concrete object size is given that width and height.
244    */
245   if (specified_width && specified_height)
246     {
247       *concrete_width = specified_width;
248       *concrete_height = specified_height;
249       return;
250     }
251
252   image_width  = _gtk_css_image_get_width (image);
253   image_height = _gtk_css_image_get_height (image);
254   image_aspect = _gtk_css_image_get_aspect_ratio (image);
255
256   /* If the specified size has neither a definite width nor height,
257    * and has no additional contraints, the dimensions of the concrete
258    * object size are calculated as follows:
259    */
260   if (specified_width == 0.0 && specified_height == 0.0)
261     {
262       /* If the object has only an intrinsic aspect ratio,
263        * the concrete object size must have that aspect ratio,
264        * and additionally be as large as possible without either
265        * its height or width exceeding the height or width of the
266        * default object size.
267        */
268       if (image_aspect > 0 && image_width == 0 && image_height == 0)
269         {
270           if (image_aspect * default_height > default_width)
271             {
272               *concrete_width = default_height * image_aspect;
273               *concrete_height = default_height;
274             }
275           else
276             {
277               *concrete_width = default_width;
278               *concrete_height = default_width / image_aspect;
279             }
280         }
281
282       /* Otherwise, the width and height of the concrete object
283        * size is the same as the object's intrinsic width and
284        * intrinsic height, if they exist.
285        * If the concrete object size is still missing a width or
286        * height, and the object has an intrinsic aspect ratio,
287        * the missing dimension is calculated from the present
288        * dimension and the intrinsic aspect ratio.
289        * Otherwise, the missing dimension is taken from the default
290        * object size. 
291        */
292       if (image_width)
293         *concrete_width = image_width;
294       else if (image_aspect)
295         *concrete_width = image_height * image_aspect;
296       else
297         *concrete_width = default_width;
298
299       if (image_height)
300         *concrete_height = image_height;
301       else if (image_aspect)
302         *concrete_height = image_width / image_aspect;
303       else
304         *concrete_height = default_height;
305
306       return;
307     }
308
309   /* If the specified size has only a width or height, but not both,
310    * then the concrete object size is given that specified width or height.
311    * The other dimension is calculated as follows:
312    * If the object has an intrinsic aspect ratio, the missing dimension of
313    * the concrete object size is calculated using the intrinsic aspect-ratio
314    * and the present dimension.
315    * Otherwise, if the missing dimension is present in the object's intrinsic
316    * dimensions, the missing dimension is taken from the object's intrinsic
317    * dimensions.
318    * Otherwise, the missing dimension of the concrete object size is taken
319    * from the default object size. 
320    */
321   if (specified_width)
322     {
323       *concrete_width = specified_width;
324       if (image_aspect)
325         *concrete_height = specified_width / image_aspect;
326       else if (image_height)
327         *concrete_height = image_height;
328       else
329         *concrete_height = default_height;
330     }
331   else
332     {
333       *concrete_height = specified_height;
334       if (image_aspect)
335         *concrete_width = specified_height * image_aspect;
336       else if (image_width)
337         *concrete_width = image_width;
338       else
339         *concrete_width = default_width;
340     }
341 }
342
343 cairo_surface_t *
344 _gtk_css_image_get_surface (GtkCssImage     *image,
345                             cairo_surface_t *target,
346                             int              surface_width,
347                             int              surface_height)
348 {
349   cairo_surface_t *result;
350   cairo_t *cr;
351
352   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
353   g_return_val_if_fail (surface_width > 0, NULL);
354   g_return_val_if_fail (surface_height > 0, NULL);
355
356   if (target)
357     result = cairo_surface_create_similar (target,
358                                            CAIRO_CONTENT_COLOR_ALPHA,
359                                            surface_width,
360                                            surface_height);
361   else
362     result = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
363                                          surface_width,
364                                          surface_height);
365
366   cr = cairo_create (result);
367   _gtk_css_image_draw (image, cr, surface_width, surface_height);
368   cairo_destroy (cr);
369
370   return result;
371 }
372
373 GType
374 gtk_css_image_get_parser_type (GtkCssParser *parser)
375 {
376   static const struct {
377     const char *prefix;
378     GType (* type_func) (void);
379   } image_types[] = {
380     { "url", _gtk_css_image_url_get_type },
381     { "-gtk-gradient", _gtk_css_image_gradient_get_type },
382     { "-gtk-win32-theme-part", _gtk_css_image_win32_get_type },
383     { "linear-gradient", _gtk_css_image_linear_get_type },
384     { "repeating-linear-gradient", _gtk_css_image_linear_get_type },
385     { "cross-fade", _gtk_css_image_cross_fade_get_type }
386   };
387   guint i;
388
389   for (i = 0; i < G_N_ELEMENTS (image_types); i++)
390     {
391       if (_gtk_css_parser_has_prefix (parser, image_types[i].prefix))
392         return image_types[i].type_func ();
393     }
394
395   return G_TYPE_INVALID;
396 }
397
398 /**
399  * _gtk_css_image_can_parse:
400  * @parser: a css parser
401  *
402  * Checks if the parser can potentially parse the given stream as an
403  * image from looking at the first token of @parser. This is useful for
404  * implementing shorthand properties. A successful parse of an image
405  * can not be guaranteed.
406  *
407  * Returns: %TURE if it looks like an image.
408  **/
409 gboolean
410 _gtk_css_image_can_parse (GtkCssParser *parser)
411 {
412   return gtk_css_image_get_parser_type (parser) != G_TYPE_INVALID;
413 }
414
415 GtkCssImage *
416 _gtk_css_image_new_parse (GtkCssParser *parser)
417 {
418   GtkCssImageClass *klass;
419   GtkCssImage *image;
420   GType image_type;
421
422   g_return_val_if_fail (parser != NULL, NULL);
423
424   image_type = gtk_css_image_get_parser_type (parser);
425   if (image_type == G_TYPE_INVALID)
426     {
427       _gtk_css_parser_error (parser, "Not a valid image");
428       return NULL;
429     }
430
431   image = g_object_new (image_type, NULL);
432
433   klass = GTK_CSS_IMAGE_GET_CLASS (image);
434   if (!klass->parse (image, parser))
435     {
436       g_object_unref (image);
437       return NULL;
438     }
439
440   return image;
441 }
442