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
24typedef enum {
25 GTK_CSS_EASE_CUBIC_BEZIER,
26 GTK_CSS_EASE_STEPS
27} GtkCssEaseType;
28
29struct _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
46static void
47gtk_css_value_ease_free (GtkCssValue *value)
48{
49 g_slice_free (GtkCssValue, value);
50}
51
52static GtkCssValue *
53gtk_css_value_ease_compute (GtkCssValue *value,
54 guint property_id,
55 GtkStyleProvider *provider,
56 GtkCssStyle *style,
57 GtkCssStyle *parent_style)
58{
59 return _gtk_css_value_ref (value);
60}
61
62static gboolean
63gtk_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
85static GtkCssValue *
86gtk_css_value_ease_transition (GtkCssValue *start,
87 GtkCssValue *end,
88 guint property_id,
89 double progress)
90{
91 return NULL;
92}
93
94static void
95gtk_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, val: "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, val: "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, val: "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, val: "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, val: "ease-in-out");
116 else
117 g_string_append_printf (string, format: "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, val: ease->u.steps.start ? "step-start" : "step-end");
125 }
126 else
127 {
128 g_string_append_printf (string, format: "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
137static const GtkCssValueClass GTK_CSS_VALUE_EASE = {
138 "GtkCssEaseValue",
139 gtk_css_value_ease_free,
140 gtk_css_value_ease_compute,
141 gtk_css_value_ease_equal,
142 gtk_css_value_ease_transition,
143 NULL,
144 NULL,
145 gtk_css_value_ease_print
146};
147
148GtkCssValue *
149_gtk_css_ease_value_new_cubic_bezier (double x1,
150 double y1,
151 double x2,
152 double y2)
153{
154 GtkCssValue *value;
155
156 g_return_val_if_fail (x1 >= 0.0, NULL);
157 g_return_val_if_fail (x1 <= 1.0, NULL);
158 g_return_val_if_fail (x2 >= 0.0, NULL);
159 g_return_val_if_fail (x2 <= 1.0, NULL);
160
161 value = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_EASE);
162
163 value->type = GTK_CSS_EASE_CUBIC_BEZIER;
164 value->u.cubic.x1 = x1;
165 value->u.cubic.y1 = y1;
166 value->u.cubic.x2 = x2;
167 value->u.cubic.y2 = y2;
168 value->is_computed = TRUE;
169
170 return value;
171}
172
173static GtkCssValue *
174_gtk_css_ease_value_new_steps (guint n_steps,
175 gboolean start)
176{
177 GtkCssValue *value;
178
179 g_return_val_if_fail (n_steps > 0, NULL);
180
181 value = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_EASE);
182
183 value->type = GTK_CSS_EASE_STEPS;
184 value->u.steps.steps = n_steps;
185 value->u.steps.start = start;
186 value->is_computed = TRUE;
187
188 return value;
189}
190
191static const struct {
192 const char *name;
193 guint is_bezier :1;
194 guint is_function :1;
195 double values[4];
196} parser_values[] = {
197 { "linear", TRUE, FALSE, { 0.0, 0.0, 1.0, 1.0 } },
198 { "ease-in-out", TRUE, FALSE, { 0.42, 0.0, 0.58, 1.0 } },
199 { "ease-in", TRUE, FALSE, { 0.42, 0.0, 1.0, 1.0 } },
200 { "ease-out", TRUE, FALSE, { 0.0, 0.0, 0.58, 1.0 } },
201 { "ease", TRUE, FALSE, { 0.25, 0.1, 0.25, 1.0 } },
202 { "step-start", FALSE, FALSE, { 1.0, 1.0, 0.0, 0.0 } },
203 { "step-end", FALSE, FALSE, { 1.0, 0.0, 0.0, 0.0 } },
204 { "steps", FALSE, TRUE, { 0.0, 0.0, 0.0, 0.0 } },
205 { "cubic-bezier", TRUE, TRUE, { 0.0, 0.0, 0.0, 0.0 } }
206};
207
208gboolean
209_gtk_css_ease_value_can_parse (GtkCssParser *parser)
210{
211 guint i;
212
213 for (i = 0; i < G_N_ELEMENTS (parser_values); i++)
214 {
215 if (parser_values[i].is_function)
216 {
217 if (gtk_css_parser_has_function (self: parser, name: parser_values[i].name))
218 return TRUE;
219 }
220 else
221 {
222 if (gtk_css_parser_has_ident (self: parser, ident: parser_values[i].name))
223 return TRUE;
224 }
225 }
226
227 return FALSE;
228}
229
230static guint
231gtk_css_ease_value_parse_cubic_bezier_arg (GtkCssParser *parser,
232 guint arg,
233 gpointer data)
234{
235 double *values = data;
236
237 if (!gtk_css_parser_consume_number (self: parser, number: &values[arg]))
238 return 0;
239
240 if (arg % 2 == 0)
241 {
242 if (values[arg] < 0 || values[arg] > 1.0)
243 {
244 gtk_css_parser_error_value (self: parser, format: "value %g out of range. Must be from 0.0 to 1.0", values[arg]);
245 return 0;
246 }
247 }
248
249 return 1;
250}
251
252static GtkCssValue *
253gtk_css_ease_value_parse_cubic_bezier (GtkCssParser *parser)
254{
255 double values[4];
256
257 if (!gtk_css_parser_consume_function (self: parser, min_args: 4, max_args: 4, parse_func: gtk_css_ease_value_parse_cubic_bezier_arg, data: values))
258 return NULL;
259
260 return _gtk_css_ease_value_new_cubic_bezier (x1: values[0], y1: values[1], x2: values[2], y2: values[3]);
261}
262
263typedef struct
264{
265 int n_steps;
266 gboolean start;
267} ParseStepsData;
268
269static guint
270gtk_css_ease_value_parse_steps_arg (GtkCssParser *parser,
271 guint arg,
272 gpointer data_)
273{
274 ParseStepsData *data = data_;
275
276 switch (arg)
277 {
278 case 0:
279 if (!gtk_css_parser_consume_integer (self: parser, number: &data->n_steps))
280 {
281 return 0;
282 }
283 else if (data->n_steps < 1)
284 {
285 gtk_css_parser_error_value (self: parser, format: "Number of steps must be > 0");
286 return 0;
287 }
288 return 1;
289
290 case 1:
291 if (gtk_css_parser_try_ident (self: parser, ident: "start"))
292 data->start = TRUE;
293 else if (gtk_css_parser_try_ident (self: parser, ident: "end"))
294 data->start = FALSE;
295 else
296 {
297 gtk_css_parser_error_syntax (self: parser, format: "Only allowed values are 'start' and 'end'");
298 return 0;
299 }
300 return 1;
301
302 default:
303 g_return_val_if_reached (0);
304 }
305}
306
307static GtkCssValue *
308gtk_css_ease_value_parse_steps (GtkCssParser *parser)
309{
310 ParseStepsData data = { 0, FALSE };
311
312 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 2, parse_func: gtk_css_ease_value_parse_steps_arg, data: &data))
313 return NULL;
314
315 return _gtk_css_ease_value_new_steps (n_steps: data.n_steps, start: data.start);
316}
317
318GtkCssValue *
319_gtk_css_ease_value_parse (GtkCssParser *parser)
320{
321 guint i;
322
323 g_return_val_if_fail (parser != NULL, NULL);
324
325 for (i = 0; i < G_N_ELEMENTS (parser_values); i++)
326 {
327 if (parser_values[i].is_function)
328 {
329 if (gtk_css_parser_has_function (self: parser, name: parser_values[i].name))
330 {
331 if (parser_values[i].is_bezier)
332 return gtk_css_ease_value_parse_cubic_bezier (parser);
333 else
334 return gtk_css_ease_value_parse_steps (parser);
335 }
336 }
337 else
338 {
339 if (gtk_css_parser_try_ident (self: parser, ident: parser_values[i].name))
340 {
341 if (parser_values[i].is_bezier)
342 return _gtk_css_ease_value_new_cubic_bezier (x1: parser_values[i].values[0],
343 y1: parser_values[i].values[1],
344 x2: parser_values[i].values[2],
345 y2: parser_values[i].values[3]);
346 else
347 return _gtk_css_ease_value_new_steps (n_steps: parser_values[i].values[0],
348 start: parser_values[i].values[1] != 0.0);
349 }
350 }
351 }
352
353 gtk_css_parser_error_syntax (self: parser, format: "Expected a valid ease value");
354 return NULL;
355}
356
357double
358_gtk_css_ease_value_transform (const GtkCssValue *ease,
359 double progress)
360{
361 g_return_val_if_fail (ease->class == &GTK_CSS_VALUE_EASE, 1.0);
362
363 if (progress <= 0)
364 return 0;
365 if (progress >= 1)
366 return 1;
367
368 switch (ease->type)
369 {
370 case GTK_CSS_EASE_CUBIC_BEZIER:
371 {
372 static const double epsilon = 0.00001;
373 double tmin, t, tmax;
374
375 tmin = 0.0;
376 tmax = 1.0;
377 t = progress;
378
379 while (tmin < tmax)
380 {
381 double sample;
382 sample = (((1.0 + 3 * ease->u.cubic.x1 - 3 * ease->u.cubic.x2) * t
383 + -6 * ease->u.cubic.x1 + 3 * ease->u.cubic.x2) * t
384 + 3 * ease->u.cubic.x1 ) * t;
385 if (fabs(x: sample - progress) < epsilon)
386 break;
387
388 if (progress > sample)
389 tmin = t;
390 else
391 tmax = t;
392 t = (tmax + tmin) * .5;
393 }
394
395 return (((1.0 + 3 * ease->u.cubic.y1 - 3 * ease->u.cubic.y2) * t
396 + -6 * ease->u.cubic.y1 + 3 * ease->u.cubic.y2) * t
397 + 3 * ease->u.cubic.y1 ) * t;
398 }
399 case GTK_CSS_EASE_STEPS:
400 progress *= ease->u.steps.steps;
401 progress = floor (x: progress) + (ease->u.steps.start ? 0 : 1);
402 return progress / ease->u.steps.steps;
403 default:
404 g_assert_not_reached ();
405 return 1.0;
406 }
407}
408
409
410

source code of gtk/gtk/gtkcsseasevalue.c