]> Pileus Git - ~andy/gtk/blob - gtk/gtkcsseasevalue.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcsseasevalue.c
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 "gtkcsseasevalueprivate.h"
21
22 #include <math.h>
23
24 typedef enum {
25   GTK_CSS_EASE_CUBIC_BEZIER,
26   GTK_CSS_EASE_STEPS
27 } GtkCssEaseType;
28
29 struct _GtkCssValue {
30   GTK_CSS_VALUE_BASE
31   GtkCssEaseType type;
32   union {
33     struct {
34       double x1;
35       double y1;
36       double x2;
37       double y2;
38     } cubic;
39     struct {
40       guint steps;
41       gboolean start;
42     } steps;
43   } u;
44 };
45
46 static void
47 gtk_css_value_ease_free (GtkCssValue *value)
48 {
49   g_slice_free (GtkCssValue, value);
50 }
51
52 static GtkCssValue *
53 gtk_css_value_ease_compute (GtkCssValue             *value,
54                             guint                    property_id,
55                             GtkStyleProviderPrivate *provider,
56                             GtkCssComputedValues    *values,
57                             GtkCssComputedValues    *parent_values,
58                             GtkCssDependencies      *dependencies)
59 {
60   return _gtk_css_value_ref (value);
61 }
62
63 static gboolean
64 gtk_css_value_ease_equal (const GtkCssValue *ease1,
65                           const GtkCssValue *ease2)
66 {
67   if (ease1->type != ease2->type)
68     return FALSE;
69   
70   switch (ease1->type)
71     {
72     case GTK_CSS_EASE_CUBIC_BEZIER:
73       return ease1->u.cubic.x1 == ease2->u.cubic.x1 &&
74              ease1->u.cubic.y1 == ease2->u.cubic.y1 &&
75              ease1->u.cubic.x2 == ease2->u.cubic.x2 &&
76              ease1->u.cubic.y2 == ease2->u.cubic.y2;
77     case GTK_CSS_EASE_STEPS:
78       return ease1->u.steps.steps == ease2->u.steps.steps &&
79              ease1->u.steps.start == ease2->u.steps.start;
80     default:
81       g_assert_not_reached ();
82       return FALSE;
83     }
84 }
85
86 static GtkCssValue *
87 gtk_css_value_ease_transition (GtkCssValue *start,
88                                GtkCssValue *end,
89                                guint        property_id,
90                                double       progress)
91 {
92   return NULL;
93 }
94
95 static void
96 gtk_css_value_ease_print (const GtkCssValue *ease,
97                           GString           *string)
98 {
99   switch (ease->type)
100     {
101     case GTK_CSS_EASE_CUBIC_BEZIER:
102       if (ease->u.cubic.x1 == 0.25 && ease->u.cubic.y1 == 0.1 &&
103           ease->u.cubic.x2 == 0.25 && ease->u.cubic.y2 == 1.0)
104         g_string_append (string, "ease");
105       else if (ease->u.cubic.x1 == 0.0 && ease->u.cubic.y1 == 0.0 &&
106                ease->u.cubic.x2 == 1.0 && ease->u.cubic.y2 == 1.0)
107         g_string_append (string, "linear");
108       else if (ease->u.cubic.x1 == 0.42 && ease->u.cubic.y1 == 0.0 &&
109                ease->u.cubic.x2 == 1.0  && ease->u.cubic.y2 == 1.0)
110         g_string_append (string, "ease-in");
111       else if (ease->u.cubic.x1 == 0.0  && ease->u.cubic.y1 == 0.0 &&
112                ease->u.cubic.x2 == 0.58 && ease->u.cubic.y2 == 1.0)
113         g_string_append (string, "ease-out");
114       else if (ease->u.cubic.x1 == 0.42 && ease->u.cubic.y1 == 0.0 &&
115                ease->u.cubic.x2 == 0.58 && ease->u.cubic.y2 == 1.0)
116         g_string_append (string, "ease-in-out");
117       else
118         g_string_append_printf (string, "cubic-bezier(%g,%g,%g,%g)", 
119                                 ease->u.cubic.x1, ease->u.cubic.y1,
120                                 ease->u.cubic.x2, ease->u.cubic.y2);
121       break;
122     case GTK_CSS_EASE_STEPS:
123       if (ease->u.steps.steps == 1)
124         {
125           g_string_append (string, ease->u.steps.start ? "step-start" : "step-end");
126         }
127       else
128         {
129           g_string_append_printf (string, "steps(%u%s)", ease->u.steps.steps, ease->u.steps.start ? ",start" : "");
130         }
131       break;
132     default:
133       g_assert_not_reached ();
134       break;
135     }
136 }
137
138 static const GtkCssValueClass GTK_CSS_VALUE_EASE = {
139   gtk_css_value_ease_free,
140   gtk_css_value_ease_compute,
141   gtk_css_value_ease_equal,
142   gtk_css_value_ease_transition,
143   gtk_css_value_ease_print
144 };
145
146 GtkCssValue *
147 _gtk_css_ease_value_new_cubic_bezier (double x1,
148                                       double y1,
149                                       double x2,
150                                       double y2)
151 {
152   GtkCssValue *value;
153
154   g_return_val_if_fail (x1 >= 0.0, NULL);
155   g_return_val_if_fail (x1 <= 1.0, NULL);
156   g_return_val_if_fail (x2 >= 0.0, NULL);
157   g_return_val_if_fail (x2 <= 1.0, NULL);
158
159   value = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_EASE);
160   
161   value->type = GTK_CSS_EASE_CUBIC_BEZIER;
162   value->u.cubic.x1 = x1;
163   value->u.cubic.y1 = y1;
164   value->u.cubic.x2 = x2;
165   value->u.cubic.y2 = y2;
166
167   return value;
168 }
169
170 static GtkCssValue *
171 _gtk_css_ease_value_new_steps (guint n_steps,
172                                gboolean start)
173 {
174   GtkCssValue *value;
175
176   g_return_val_if_fail (n_steps > 0, NULL);
177
178   value = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_EASE);
179   
180   value->type = GTK_CSS_EASE_STEPS;
181   value->u.steps.steps = n_steps;
182   value->u.steps.start = start;
183
184   return value;
185 }
186
187 static const struct {
188   const char *name;
189   guint is_bezier :1;
190   guint needs_custom :1;
191   double values[4];
192 } parser_values[] = {
193   { "linear",       TRUE,  FALSE, { 0.0,  0.0, 1.0,  1.0 } },
194   { "ease-in-out",  TRUE,  FALSE, { 0.42, 0.0, 0.58, 1.0 } },
195   { "ease-in",      TRUE,  FALSE, { 0.42, 0.0, 1.0,  1.0 } },
196   { "ease-out",     TRUE,  FALSE, { 0.0,  0.0, 0.58, 1.0 } },
197   { "ease",         TRUE,  FALSE, { 0.25, 0.1, 0.25, 1.0 } },
198   { "step-start",   FALSE, FALSE, { 1.0,  1.0, 0.0,  0.0 } },
199   { "step-end",     FALSE, FALSE, { 1.0,  0.0, 0.0,  0.0 } },
200   { "steps",        FALSE, TRUE,  { 0.0,  0.0, 0.0,  0.0 } },
201   { "cubic-bezier", TRUE,  TRUE,  { 0.0,  0.0, 0.0,  0.0 } }
202 };
203
204 gboolean
205 _gtk_css_ease_value_can_parse (GtkCssParser *parser)
206 {
207   guint i;
208
209   for (i = 0; i < G_N_ELEMENTS (parser_values); i++)
210     {
211       if (_gtk_css_parser_has_prefix (parser, parser_values[i].name))
212         return TRUE;
213     }
214
215   return FALSE;
216 }
217
218 static GtkCssValue *
219 gtk_css_ease_value_parse_cubic_bezier (GtkCssParser *parser)
220 {
221   double values[4];
222   guint i;
223
224   for (i = 0; i < 4; i++)
225     {
226       if (!_gtk_css_parser_try (parser, i ? "," : "(", TRUE))
227         {
228           _gtk_css_parser_error (parser, "Expected '%s'", i ? "," : "(");
229           return NULL;
230         }
231       if (!_gtk_css_parser_try_double (parser, &values[i]))
232         {
233           _gtk_css_parser_error (parser, "Expected a number");
234           return NULL;
235         }
236       if ((i == 0 || i == 2) &&
237           (values[i] < 0 || values[i] > 1.0))
238         {
239           _gtk_css_parser_error (parser, "value %g out of range. Must be from 0.0 to 1.0", values[i]);
240           return NULL;
241         }
242     }
243
244   if (!_gtk_css_parser_try (parser, ")", TRUE))
245     {
246       _gtk_css_parser_error (parser, "Missing closing ')' for cubic-bezier");
247       return NULL;
248     }
249
250   return _gtk_css_ease_value_new_cubic_bezier (values[0], values[1], values[2], values[3]);
251 }
252
253 static GtkCssValue *
254 gtk_css_ease_value_parse_steps (GtkCssParser *parser)
255 {
256   guint n_steps;
257   gboolean start;
258
259   if (!_gtk_css_parser_try (parser, "(", TRUE))
260     {
261       _gtk_css_parser_error (parser, "Expected '('");
262       return NULL;
263     }
264
265   if (!_gtk_css_parser_try_uint (parser, &n_steps))
266     {
267       _gtk_css_parser_error (parser, "Expected number of steps");
268       return NULL;
269     }
270
271   if (_gtk_css_parser_try (parser, ",", TRUE))
272     {
273       if (_gtk_css_parser_try (parser, "start", TRUE))
274         start = TRUE;
275       else if (_gtk_css_parser_try (parser, "end", TRUE))
276         start = FALSE;
277       else
278         {
279           _gtk_css_parser_error (parser, "Only allowed values are 'start' and 'end'");
280           return NULL;
281         }
282     }
283   else
284     start = FALSE;
285
286   if (!_gtk_css_parser_try (parser, ")", TRUE))
287     {
288       _gtk_css_parser_error (parser, "Missing closing ')' for steps");
289       return NULL;
290     }
291
292   return _gtk_css_ease_value_new_steps (n_steps, start);
293 }
294
295 GtkCssValue *
296 _gtk_css_ease_value_parse (GtkCssParser *parser)
297 {
298   guint i;
299
300   g_return_val_if_fail (parser != NULL, NULL);
301
302   for (i = 0; i < G_N_ELEMENTS (parser_values); i++)
303     {
304       if (_gtk_css_parser_try (parser, parser_values[i].name, FALSE))
305         {
306           if (parser_values[i].needs_custom)
307             {
308               if (parser_values[i].is_bezier)
309                 return gtk_css_ease_value_parse_cubic_bezier (parser);
310               else
311                 return gtk_css_ease_value_parse_steps (parser);
312             }
313
314           _gtk_css_parser_skip_whitespace (parser);
315
316           if (parser_values[i].is_bezier)
317             return _gtk_css_ease_value_new_cubic_bezier (parser_values[i].values[0],
318                                                          parser_values[i].values[1],
319                                                          parser_values[i].values[2],
320                                                          parser_values[i].values[3]);
321           else
322             return _gtk_css_ease_value_new_steps (parser_values[i].values[0],
323                                                   parser_values[i].values[1] != 0.0);
324         }
325     }
326
327   _gtk_css_parser_error (parser, "Unknown value");
328   return NULL;
329 }
330
331 double
332 _gtk_css_ease_value_transform (const GtkCssValue *ease,
333                                double             progress)
334 {
335   g_return_val_if_fail (ease->class == &GTK_CSS_VALUE_EASE, 1.0);
336
337   if (progress <= 0)
338     return 0;
339   if (progress >= 1)
340     return 1;
341
342   switch (ease->type)
343     {
344     case GTK_CSS_EASE_CUBIC_BEZIER:
345       {
346         static const double epsilon = 0.00001;
347         double tmin, t, tmax;
348
349         tmin = 0.0;
350         tmax = 1.0;
351         t = progress;
352
353         while (tmin < tmax)
354           {
355              double sample;
356              sample = (((1.0 + 3 * ease->u.cubic.x1 - 3 * ease->u.cubic.x2) * t
357                        +      -6 * ease->u.cubic.x1 + 3 * ease->u.cubic.x2) * t
358                        +       3 * ease->u.cubic.x1                       ) * t;
359              if (fabs(sample - progress) < epsilon)
360                break;
361
362              if (progress > sample)
363                tmin = t;
364              else
365                tmax = t;
366              t = (tmax + tmin) * .5;
367           }
368
369         return (((1.0 + 3 * ease->u.cubic.y1 - 3 * ease->u.cubic.y2) * t
370                 +      -6 * ease->u.cubic.y1 + 3 * ease->u.cubic.y2) * t
371                 +       3 * ease->u.cubic.y1                       ) * t;
372       }
373     case GTK_CSS_EASE_STEPS:
374       progress *= ease->u.steps.steps;
375       progress = floor (progress) + (ease->u.steps.start ? 0 : 1);
376       return progress / ease->u.steps.steps;
377     default:
378       g_assert_not_reached ();
379       return 1.0;
380     }
381 }
382
383