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