1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 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 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 
18 #include "config.h"
19 
20 #include "gtkcssbgsizevalueprivate.h"
21 
22 #include "gtkcssnumbervalueprivate.h"
23 
24 struct _GtkCssValue {
25   GTK_CSS_VALUE_BASE
26   guint cover :1;
27   guint contain :1;
28   GtkCssValue *x;
29   GtkCssValue *y;
30 };
31 
32 static void
gtk_css_value_bg_size_free(GtkCssValue * value)33 gtk_css_value_bg_size_free (GtkCssValue *value)
34 {
35   if (value->x)
36     _gtk_css_value_unref (value->x);
37   if (value->y)
38     _gtk_css_value_unref (value->y);
39 
40   g_slice_free (GtkCssValue, value);
41 }
42 
43 static GtkCssValue *
gtk_css_value_bg_size_compute(GtkCssValue * value,guint property_id,GtkStyleProvider * provider,GtkCssStyle * style,GtkCssStyle * parent_style)44 gtk_css_value_bg_size_compute (GtkCssValue      *value,
45                                guint             property_id,
46                                GtkStyleProvider *provider,
47                                GtkCssStyle      *style,
48                                GtkCssStyle      *parent_style)
49 {
50   GtkCssValue *x, *y;
51 
52   if (value->x == NULL && value->y == NULL)
53     return _gtk_css_value_ref (value);
54 
55   x = y = NULL;
56 
57   if (value->x)
58     x = _gtk_css_value_compute (value->x, property_id, provider, style, parent_style);
59 
60   if (value->y)
61     y = _gtk_css_value_compute (value->y, property_id, provider, style, parent_style);
62 
63   if (x == value->x && y == value->y)
64     {
65       if (x)
66         _gtk_css_value_unref (x);
67       if (y)
68         _gtk_css_value_unref (y);
69 
70       return _gtk_css_value_ref (value);
71     }
72 
73   return _gtk_css_bg_size_value_new (value->x ? x : NULL,
74                                      value->y ? y : NULL);
75 }
76 
77 static gboolean
gtk_css_value_bg_size_equal(const GtkCssValue * value1,const GtkCssValue * value2)78 gtk_css_value_bg_size_equal (const GtkCssValue *value1,
79                              const GtkCssValue *value2)
80 {
81   return value1->cover == value2->cover &&
82          value1->contain == value2->contain &&
83          (value1->x == value2->x ||
84           (value1->x != NULL && value2->x != NULL &&
85            _gtk_css_value_equal (value1->x, value2->x))) &&
86          (value1->y == value2->y ||
87           (value1->y != NULL && value2->y != NULL &&
88            _gtk_css_value_equal (value1->y, value2->y)));
89 }
90 
91 static GtkCssValue *
gtk_css_value_bg_size_transition(GtkCssValue * start,GtkCssValue * end,guint property_id,double progress)92 gtk_css_value_bg_size_transition (GtkCssValue *start,
93                                   GtkCssValue *end,
94                                   guint        property_id,
95                                   double       progress)
96 {
97   GtkCssValue *x, *y;
98 
99   if (start->cover)
100     return end->cover ? _gtk_css_value_ref (end) : NULL;
101   if (start->contain)
102     return end->contain ? _gtk_css_value_ref (end) : NULL;
103 
104   if ((start->x != NULL) ^ (end->x != NULL) ||
105       (start->y != NULL) ^ (end->y != NULL))
106     return NULL;
107 
108   if (start->x)
109     {
110       x = _gtk_css_value_transition (start->x, end->x, property_id, progress);
111       if (x == NULL)
112         return NULL;
113     }
114   else
115     x = NULL;
116 
117   if (start->y)
118     {
119       y = _gtk_css_value_transition (start->y, end->y, property_id, progress);
120       if (y == NULL)
121         {
122           _gtk_css_value_unref (x);
123           return NULL;
124         }
125     }
126   else
127     y = NULL;
128 
129   return _gtk_css_bg_size_value_new (x, y);
130 }
131 
132 static void
gtk_css_value_bg_size_print(const GtkCssValue * value,GString * string)133 gtk_css_value_bg_size_print (const GtkCssValue *value,
134                              GString           *string)
135 {
136   if (value->cover)
137     g_string_append (string, "cover");
138   else if (value->contain)
139     g_string_append (string, "contain");
140   else
141     {
142       if (value->x == NULL)
143         g_string_append (string, "auto");
144       else
145         _gtk_css_value_print (value->x, string);
146 
147       if (value->y)
148         {
149           g_string_append_c (string, ' ');
150           _gtk_css_value_print (value->y, string);
151         }
152     }
153 }
154 
155 static const GtkCssValueClass GTK_CSS_VALUE_BG_SIZE = {
156   "GtkCssBgSizeValue",
157   gtk_css_value_bg_size_free,
158   gtk_css_value_bg_size_compute,
159   gtk_css_value_bg_size_equal,
160   gtk_css_value_bg_size_transition,
161   NULL,
162   NULL,
163   gtk_css_value_bg_size_print
164 };
165 
166 static GtkCssValue auto_singleton = { &GTK_CSS_VALUE_BG_SIZE, 1, TRUE, FALSE, FALSE, NULL, NULL };
167 static GtkCssValue cover_singleton = { &GTK_CSS_VALUE_BG_SIZE, 1, TRUE, TRUE, FALSE, NULL, NULL };
168 static GtkCssValue contain_singleton = { &GTK_CSS_VALUE_BG_SIZE, 1, TRUE, FALSE, TRUE, NULL, NULL };
169 
170 GtkCssValue *
_gtk_css_bg_size_value_new(GtkCssValue * x,GtkCssValue * y)171 _gtk_css_bg_size_value_new (GtkCssValue *x,
172                             GtkCssValue *y)
173 {
174   GtkCssValue *result;
175 
176   if (x == NULL && y == NULL)
177     return _gtk_css_value_ref (&auto_singleton);
178 
179   result = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_BG_SIZE);
180   result->x = x;
181   result->y = y;
182   result->is_computed = (!x || gtk_css_value_is_computed (x)) &&
183                         (!y || gtk_css_value_is_computed (y));
184 
185   return result;
186 }
187 
188 GtkCssValue *
_gtk_css_bg_size_value_parse(GtkCssParser * parser)189 _gtk_css_bg_size_value_parse (GtkCssParser *parser)
190 {
191   GtkCssValue *x, *y;
192 
193   if (gtk_css_parser_try_ident (parser, "cover"))
194     return _gtk_css_value_ref (&cover_singleton);
195   else if (gtk_css_parser_try_ident (parser, "contain"))
196     return _gtk_css_value_ref (&contain_singleton);
197 
198   if (gtk_css_parser_try_ident (parser, "auto"))
199     x = NULL;
200   else
201     {
202       x = _gtk_css_number_value_parse (parser,
203                                        GTK_CSS_POSITIVE_ONLY
204                                        | GTK_CSS_PARSE_PERCENT
205                                        | GTK_CSS_PARSE_LENGTH);
206       if (x == NULL)
207         return NULL;
208     }
209 
210   if (gtk_css_parser_try_ident (parser, "auto"))
211     y = NULL;
212   else if (!gtk_css_number_value_can_parse (parser))
213     y = NULL;
214   else
215     {
216       y = _gtk_css_number_value_parse (parser,
217                                        GTK_CSS_POSITIVE_ONLY
218                                        | GTK_CSS_PARSE_PERCENT
219                                        | GTK_CSS_PARSE_LENGTH);
220       if (y == NULL)
221         {
222           _gtk_css_value_unref (x);
223           return NULL;
224         }
225     }
226 
227   return _gtk_css_bg_size_value_new (x, y);
228 }
229 
230 static void
gtk_css_bg_size_compute_size_for_cover_contain(gboolean cover,GtkCssImage * image,double width,double height,double * concrete_width,double * concrete_height)231 gtk_css_bg_size_compute_size_for_cover_contain (gboolean     cover,
232                                                 GtkCssImage *image,
233                                                 double       width,
234                                                 double       height,
235                                                 double      *concrete_width,
236                                                 double      *concrete_height)
237 {
238   double aspect, image_aspect;
239 
240   image_aspect = _gtk_css_image_get_aspect_ratio (image);
241   if (image_aspect == 0.0)
242     {
243       *concrete_width = width;
244       *concrete_height = height;
245       return;
246     }
247 
248   aspect = width / height;
249 
250   if ((aspect >= image_aspect && cover) ||
251       (aspect < image_aspect && !cover))
252     {
253       *concrete_width = width;
254       *concrete_height = width / image_aspect;
255     }
256   else
257     {
258       *concrete_height = height;
259       *concrete_width = height * image_aspect;
260     }
261 }
262 
263 void
_gtk_css_bg_size_value_compute_size(const GtkCssValue * value,GtkCssImage * image,double area_width,double area_height,double * out_width,double * out_height)264 _gtk_css_bg_size_value_compute_size (const GtkCssValue *value,
265                                      GtkCssImage       *image,
266                                      double             area_width,
267                                      double             area_height,
268                                      double            *out_width,
269                                      double            *out_height)
270 {
271   g_return_if_fail (value->class == &GTK_CSS_VALUE_BG_SIZE);
272 
273   if (value->contain || value->cover)
274     {
275       gtk_css_bg_size_compute_size_for_cover_contain (value->cover,
276                                                       image,
277                                                       area_width, area_height,
278                                                       out_width, out_height);
279     }
280   else
281     {
282       double x, y;
283 
284       /* note: 0 does the right thing later for 'auto' */
285       x = value->x ? _gtk_css_number_value_get (value->x, area_width) : 0;
286       y = value->y ? _gtk_css_number_value_get (value->y, area_height) : 0;
287 
288       if ((x <= 0 && value->x) ||
289           (y <= 0 && value->y))
290         {
291           *out_width = 0;
292           *out_height = 0;
293         }
294       else
295         {
296           _gtk_css_image_get_concrete_size (image,
297                                             x, y,
298                                             area_width, area_height,
299                                             out_width, out_height);
300         }
301     }
302 }
303 
304