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