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