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