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