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, >K_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, >K_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 == >K_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