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 "gtkcsskeyframesprivate.h" |
21 | |
22 | #include "gtkcssarrayvalueprivate.h" |
23 | #include "gtkcssshorthandpropertyprivate.h" |
24 | #include "gtkcssstylepropertyprivate.h" |
25 | #include "gtkstylepropertyprivate.h" |
26 | |
27 | #include "gtkprivate.h" |
28 | |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | |
32 | struct _GtkCssKeyframes { |
33 | int ref_count; /* ref count */ |
34 | int n_keyframes; /* number of keyframes (at least 2 for 0% and 100% */ |
35 | double *keyframe_progress; /* ordered array of n_keyframes of [0..1] */ |
36 | int n_properties; /* number of properties used by keyframes */ |
37 | guint *property_ids; /* ordered array of n_properties property ids */ |
38 | GtkCssValue **values; /* 2D array: n_keyframes * n_properties of (value or NULL) for all the keyframes */ |
39 | }; |
40 | |
41 | #define KEYFRAMES_VALUE(keyframes, k, p) ((keyframes)->values[(k) * (keyframes)->n_properties + (p)]) |
42 | |
43 | GtkCssKeyframes * |
44 | _gtk_css_keyframes_ref (GtkCssKeyframes *keyframes) |
45 | { |
46 | g_return_val_if_fail (keyframes != NULL, NULL); |
47 | |
48 | keyframes->ref_count++; |
49 | |
50 | return keyframes; |
51 | } |
52 | |
53 | void |
54 | _gtk_css_keyframes_unref (GtkCssKeyframes *keyframes) |
55 | { |
56 | guint k, p; |
57 | |
58 | g_return_if_fail (keyframes != NULL); |
59 | |
60 | keyframes->ref_count--; |
61 | if (keyframes->ref_count > 0) |
62 | return; |
63 | |
64 | g_free (mem: keyframes->keyframe_progress); |
65 | g_free (mem: keyframes->property_ids); |
66 | |
67 | for (k = 0; k < keyframes->n_keyframes; k++) |
68 | { |
69 | for (p = 0; p < keyframes->n_properties; p++) |
70 | { |
71 | _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p)); |
72 | KEYFRAMES_VALUE (keyframes, k, p) = NULL; |
73 | } |
74 | } |
75 | g_free (mem: keyframes->values); |
76 | |
77 | g_slice_free (GtkCssKeyframes, keyframes); |
78 | } |
79 | |
80 | static guint |
81 | gtk_css_keyframes_add_keyframe (GtkCssKeyframes *keyframes, |
82 | double progress) |
83 | { |
84 | guint k, p; |
85 | |
86 | for (k = 0; k < keyframes->n_keyframes; k++) |
87 | { |
88 | if (keyframes->keyframe_progress[k] == progress) |
89 | { |
90 | for (p = 0; p < keyframes->n_properties; p++) |
91 | { |
92 | if (KEYFRAMES_VALUE (keyframes, k, p) == NULL) |
93 | continue; |
94 | |
95 | _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p)); |
96 | KEYFRAMES_VALUE (keyframes, k, p) = NULL; |
97 | |
98 | /* XXX: GC properties that are now unset |
99 | * in all keyframes? |
100 | */ |
101 | } |
102 | return k; |
103 | } |
104 | else if (keyframes->keyframe_progress[k] > progress) |
105 | break; |
106 | } |
107 | |
108 | keyframes->n_keyframes++; |
109 | keyframes->keyframe_progress = g_realloc (mem: keyframes->keyframe_progress, n_bytes: sizeof (double) * keyframes->n_keyframes); |
110 | memmove (dest: keyframes->keyframe_progress + k + 1, src: keyframes->keyframe_progress + k, n: sizeof (double) * (keyframes->n_keyframes - k - 1)); |
111 | keyframes->keyframe_progress[k] = progress; |
112 | |
113 | if (keyframes->n_properties) |
114 | { |
115 | gsize size = sizeof (GtkCssValue *) * keyframes->n_properties; |
116 | |
117 | keyframes->values = g_realloc (mem: keyframes->values, n_bytes: sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties); |
118 | memmove (dest: &KEYFRAMES_VALUE (keyframes, k + 1, 0), src: &KEYFRAMES_VALUE (keyframes, k, 0), n: size * (keyframes->n_keyframes - k - 1)); |
119 | memset (s: &KEYFRAMES_VALUE (keyframes, k, 0), c: 0, n: size); |
120 | } |
121 | |
122 | return k; |
123 | } |
124 | |
125 | static guint |
126 | gtk_css_keyframes_lookup_property (GtkCssKeyframes *keyframes, |
127 | guint property_id) |
128 | { |
129 | guint p; |
130 | |
131 | for (p = 0; p < keyframes->n_properties; p++) |
132 | { |
133 | if (keyframes->property_ids[p] == property_id) |
134 | return p; |
135 | else if (keyframes->property_ids[p] > property_id) |
136 | break; |
137 | } |
138 | |
139 | keyframes->n_properties++; |
140 | keyframes->property_ids = g_realloc (mem: keyframes->property_ids, n_bytes: sizeof (guint) * keyframes->n_properties); |
141 | memmove (dest: keyframes->property_ids + p + 1, src: keyframes->property_ids + p, n: sizeof (guint) * (keyframes->n_properties - p - 1)); |
142 | keyframes->property_ids[p] = property_id; |
143 | |
144 | if (keyframes->n_properties > 1) |
145 | { |
146 | guint old_n_properties = keyframes->n_properties - 1; |
147 | int k; |
148 | |
149 | keyframes->values = g_realloc (mem: keyframes->values, n_bytes: sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties); |
150 | |
151 | if (p + 1 < keyframes->n_properties) |
152 | { |
153 | memmove (dest: &KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p + 1), |
154 | src: &keyframes->values[(keyframes->n_keyframes - 1) * old_n_properties + p], |
155 | n: sizeof (GtkCssValue *) * (keyframes->n_properties - p - 1)); |
156 | } |
157 | KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p) = NULL; |
158 | |
159 | for (k = keyframes->n_keyframes - 2; k >= 0; k--) |
160 | { |
161 | memmove (dest: &KEYFRAMES_VALUE (keyframes, k, p + 1), |
162 | src: &keyframes->values[k * old_n_properties + p], |
163 | n: sizeof (GtkCssValue *) * old_n_properties); |
164 | KEYFRAMES_VALUE (keyframes, k, p) = NULL; |
165 | } |
166 | } |
167 | else |
168 | { |
169 | keyframes->values = g_new0 (GtkCssValue *, keyframes->n_keyframes); |
170 | } |
171 | |
172 | return p; |
173 | } |
174 | |
175 | static GtkCssKeyframes * |
176 | gtk_css_keyframes_alloc (void) |
177 | { |
178 | GtkCssKeyframes *keyframes; |
179 | |
180 | keyframes = g_slice_new0 (GtkCssKeyframes); |
181 | keyframes->ref_count = 1; |
182 | |
183 | return keyframes; |
184 | } |
185 | |
186 | static GtkCssKeyframes * |
187 | gtk_css_keyframes_new (void) |
188 | { |
189 | GtkCssKeyframes *keyframes; |
190 | |
191 | keyframes = gtk_css_keyframes_alloc (); |
192 | |
193 | gtk_css_keyframes_add_keyframe (keyframes, progress: 0); |
194 | gtk_css_keyframes_add_keyframe (keyframes, progress: 1); |
195 | |
196 | return keyframes; |
197 | } |
198 | |
199 | static gboolean |
200 | keyframes_set_value (GtkCssKeyframes *keyframes, |
201 | guint k, |
202 | GtkCssStyleProperty *property, |
203 | GtkCssValue *value) |
204 | { |
205 | guint p; |
206 | |
207 | if (!_gtk_css_style_property_is_animated (property)) |
208 | return FALSE; |
209 | |
210 | p = gtk_css_keyframes_lookup_property (keyframes, property_id: _gtk_css_style_property_get_id (property)); |
211 | |
212 | if (KEYFRAMES_VALUE (keyframes, k, p)) |
213 | _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p)); |
214 | |
215 | KEYFRAMES_VALUE (keyframes, k, p) = _gtk_css_value_ref (value); |
216 | |
217 | return TRUE; |
218 | } |
219 | |
220 | static gboolean |
221 | gtk_css_keyframes_parse_declaration (GtkCssKeyframes *keyframes, |
222 | guint k, |
223 | GtkCssParser *parser) |
224 | { |
225 | GtkStyleProperty *property; |
226 | GtkCssValue *value; |
227 | char *name; |
228 | |
229 | name = gtk_css_parser_consume_ident (self: parser); |
230 | if (name == NULL) |
231 | { |
232 | if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
233 | gtk_css_parser_error_syntax (self: parser, format: "Expected a property name" ); |
234 | return FALSE; |
235 | } |
236 | |
237 | property = _gtk_style_property_lookup (name); |
238 | if (property == NULL) |
239 | { |
240 | gtk_css_parser_error_value (self: parser, format: "No property named '%s'" , name); |
241 | g_free (mem: name); |
242 | return FALSE; |
243 | } |
244 | |
245 | g_free (mem: name); |
246 | |
247 | if (!gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COLON)) |
248 | { |
249 | gtk_css_parser_error_syntax (self: parser, format: "Expected a ':'" ); |
250 | return FALSE; |
251 | } |
252 | |
253 | value = _gtk_style_property_parse_value (property, parser); |
254 | if (value == NULL) |
255 | return FALSE; |
256 | |
257 | if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
258 | { |
259 | gtk_css_parser_error_syntax (self: parser, format: "Junk at end of value" ); |
260 | _gtk_css_value_unref (value); |
261 | return FALSE; |
262 | } |
263 | |
264 | if (GTK_IS_CSS_SHORTHAND_PROPERTY (property)) |
265 | { |
266 | GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property); |
267 | gboolean animatable = FALSE; |
268 | guint i; |
269 | |
270 | for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++) |
271 | { |
272 | GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, property: i); |
273 | GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i); |
274 | |
275 | animatable |= keyframes_set_value (keyframes, k, property: child, value: sub); |
276 | } |
277 | |
278 | if (!animatable) |
279 | gtk_css_parser_error_value (self: parser, format: "shorthand '%s' cannot be animated" , _gtk_style_property_get_name (property)); |
280 | } |
281 | else if (GTK_IS_CSS_STYLE_PROPERTY (property)) |
282 | { |
283 | if (!keyframes_set_value (keyframes, k, GTK_CSS_STYLE_PROPERTY (property), value)) |
284 | gtk_css_parser_error_value (self: parser, format: "Cannot animate property '%s'" , _gtk_style_property_get_name (property)); |
285 | } |
286 | else |
287 | { |
288 | g_assert_not_reached (); |
289 | } |
290 | |
291 | _gtk_css_value_unref (value); |
292 | |
293 | return TRUE; |
294 | } |
295 | |
296 | static gboolean |
297 | gtk_css_keyframes_parse_block (GtkCssKeyframes *keyframes, |
298 | guint k, |
299 | GtkCssParser *parser) |
300 | { |
301 | if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_OPEN_CURLY)) |
302 | { |
303 | gtk_css_parser_error_syntax (self: parser, format: "Expected '{'" ); |
304 | return FALSE; |
305 | } |
306 | |
307 | gtk_css_parser_start_block (self: parser); |
308 | |
309 | while (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
310 | { |
311 | gtk_css_parser_start_semicolon_block (self: parser, alternative_token: GTK_CSS_TOKEN_EOF); |
312 | gtk_css_keyframes_parse_declaration (keyframes, k, parser); |
313 | gtk_css_parser_end_block (self: parser); |
314 | } |
315 | |
316 | gtk_css_parser_end_block (self: parser); |
317 | |
318 | return TRUE; |
319 | } |
320 | |
321 | GtkCssKeyframes * |
322 | _gtk_css_keyframes_parse (GtkCssParser *parser) |
323 | { |
324 | GtkCssKeyframes *keyframes; |
325 | double progress; |
326 | guint k; |
327 | |
328 | g_return_val_if_fail (parser != NULL, NULL); |
329 | |
330 | keyframes = gtk_css_keyframes_new (); |
331 | |
332 | while (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
333 | { |
334 | if (gtk_css_parser_try_ident (self: parser, ident: "from" )) |
335 | progress = 0; |
336 | else if (gtk_css_parser_try_ident (self: parser, ident: "to" )) |
337 | progress = 1; |
338 | else if (gtk_css_parser_consume_percentage (self: parser, number: &progress)) |
339 | { |
340 | if (progress < 0 || progress > 100) |
341 | { |
342 | /* XXX: should we skip over the block here? */ |
343 | gtk_css_parser_error_value (self: parser, format: "percentages must be between 0%% and 100%%" ); |
344 | _gtk_css_keyframes_unref (keyframes); |
345 | return NULL; |
346 | } |
347 | progress /= 100; |
348 | } |
349 | else |
350 | { |
351 | _gtk_css_keyframes_unref (keyframes); |
352 | return NULL; |
353 | } |
354 | |
355 | k = gtk_css_keyframes_add_keyframe (keyframes, progress); |
356 | |
357 | if (!gtk_css_keyframes_parse_block (keyframes, k, parser)) |
358 | { |
359 | _gtk_css_keyframes_unref (keyframes); |
360 | return NULL; |
361 | } |
362 | } |
363 | |
364 | return keyframes; |
365 | } |
366 | |
367 | static int |
368 | compare_property_by_name (gconstpointer a, |
369 | gconstpointer b, |
370 | gpointer data) |
371 | { |
372 | GtkCssKeyframes *keyframes = data; |
373 | |
374 | return strcmp (s1: _gtk_style_property_get_name (GTK_STYLE_PROPERTY ( |
375 | _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) a]))), |
376 | s2: _gtk_style_property_get_name (GTK_STYLE_PROPERTY ( |
377 | _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) b])))); |
378 | } |
379 | |
380 | void |
381 | _gtk_css_keyframes_print (GtkCssKeyframes *keyframes, |
382 | GString *string) |
383 | { |
384 | guint k, p; |
385 | guint *sorted; |
386 | |
387 | g_return_if_fail (keyframes != NULL); |
388 | g_return_if_fail (string != NULL); |
389 | |
390 | sorted = g_new (guint, keyframes->n_properties); |
391 | for (p = 0; p < keyframes->n_properties; p++) |
392 | sorted[p] = p; |
393 | g_qsort_with_data (pbase: sorted, total_elems: keyframes->n_properties, size: sizeof (guint), compare_func: compare_property_by_name, user_data: keyframes); |
394 | |
395 | for (k = 0; k < keyframes->n_keyframes; k++) |
396 | { |
397 | /* useful for 0% and 100% which might be empty */ |
398 | gboolean opened = FALSE; |
399 | |
400 | for (p = 0; p < keyframes->n_properties; p++) |
401 | { |
402 | if (KEYFRAMES_VALUE (keyframes, k, sorted[p]) == NULL) |
403 | continue; |
404 | |
405 | if (!opened) |
406 | { |
407 | if (keyframes->keyframe_progress[k] == 0.0) |
408 | g_string_append (string, val: " from {\n" ); |
409 | else if (keyframes->keyframe_progress[k] == 1.0) |
410 | g_string_append (string, val: " to {\n" ); |
411 | else |
412 | g_string_append_printf (string, format: " %g%% {\n" , keyframes->keyframe_progress[k] * 100); |
413 | opened = TRUE; |
414 | } |
415 | |
416 | g_string_append_printf (string, format: " %s: " , _gtk_style_property_get_name ( |
417 | GTK_STYLE_PROPERTY ( |
418 | _gtk_css_style_property_lookup_by_id ( |
419 | keyframes->property_ids[sorted[p]])))); |
420 | _gtk_css_value_print (KEYFRAMES_VALUE (keyframes, k, sorted[p]), string); |
421 | g_string_append (string, val: ";\n" ); |
422 | } |
423 | |
424 | if (opened) |
425 | g_string_append (string, val: " }\n" ); |
426 | } |
427 | |
428 | g_free (mem: sorted); |
429 | } |
430 | |
431 | GtkCssKeyframes * |
432 | _gtk_css_keyframes_compute (GtkCssKeyframes *keyframes, |
433 | GtkStyleProvider *provider, |
434 | GtkCssStyle *style, |
435 | GtkCssStyle *parent_style) |
436 | { |
437 | GtkCssKeyframes *resolved; |
438 | guint k, p; |
439 | |
440 | g_return_val_if_fail (keyframes != NULL, NULL); |
441 | g_return_val_if_fail (GTK_IS_STYLE_PROVIDER (provider), NULL); |
442 | g_return_val_if_fail (GTK_IS_CSS_STYLE (style), NULL); |
443 | g_return_val_if_fail (parent_style == NULL || GTK_IS_CSS_STYLE (parent_style), NULL); |
444 | |
445 | resolved = gtk_css_keyframes_alloc (); |
446 | resolved->n_keyframes = keyframes->n_keyframes; |
447 | resolved->keyframe_progress = g_memdup2 (mem: keyframes->keyframe_progress, byte_size: keyframes->n_keyframes * sizeof (double)); |
448 | resolved->n_properties = keyframes->n_properties; |
449 | resolved->property_ids = g_memdup2 (mem: keyframes->property_ids, byte_size: keyframes->n_properties * sizeof (guint)); |
450 | resolved->values = g_new0 (GtkCssValue *, resolved->n_keyframes * resolved->n_properties); |
451 | |
452 | for (p = 0; p < resolved->n_properties; p++) |
453 | { |
454 | for (k = 0; k < resolved->n_keyframes; k++) |
455 | { |
456 | if (KEYFRAMES_VALUE (keyframes, k, p) == NULL) |
457 | continue; |
458 | |
459 | KEYFRAMES_VALUE (resolved, k, p) = _gtk_css_value_compute (KEYFRAMES_VALUE (keyframes, k, p), |
460 | property_id: resolved->property_ids[p], |
461 | provider, |
462 | style, |
463 | parent_style); |
464 | } |
465 | } |
466 | |
467 | return resolved; |
468 | } |
469 | |
470 | guint |
471 | _gtk_css_keyframes_get_n_properties (GtkCssKeyframes *keyframes) |
472 | { |
473 | g_return_val_if_fail (keyframes != NULL, 0); |
474 | |
475 | return keyframes->n_properties; |
476 | } |
477 | |
478 | guint |
479 | _gtk_css_keyframes_get_property_id (GtkCssKeyframes *keyframes, |
480 | guint id) |
481 | { |
482 | g_return_val_if_fail (keyframes != NULL, 0); |
483 | g_return_val_if_fail (id < keyframes->n_properties, 0); |
484 | |
485 | return keyframes->property_ids[id]; |
486 | } |
487 | |
488 | GtkCssValue * |
489 | _gtk_css_keyframes_get_value (GtkCssKeyframes *keyframes, |
490 | guint id, |
491 | double progress, |
492 | GtkCssValue *default_value) |
493 | { |
494 | GtkCssValue *start_value, *end_value, *result; |
495 | double start_progress, end_progress; |
496 | guint k; |
497 | |
498 | g_return_val_if_fail (keyframes != NULL, 0); |
499 | g_return_val_if_fail (id < keyframes->n_properties, 0); |
500 | |
501 | start_value = default_value; |
502 | start_progress = 0.0; |
503 | end_value = default_value; |
504 | end_progress = 1.0; |
505 | |
506 | for (k = 0; k < keyframes->n_keyframes; k++) |
507 | { |
508 | if (KEYFRAMES_VALUE (keyframes, k, id) == NULL) |
509 | continue; |
510 | |
511 | if (keyframes->keyframe_progress[k] == progress) |
512 | { |
513 | return _gtk_css_value_ref (KEYFRAMES_VALUE (keyframes, k, id)); |
514 | } |
515 | else if (keyframes->keyframe_progress[k] < progress) |
516 | { |
517 | start_value = KEYFRAMES_VALUE (keyframes, k, id); |
518 | start_progress = keyframes->keyframe_progress[k]; |
519 | } |
520 | else |
521 | { |
522 | end_value = KEYFRAMES_VALUE (keyframes, k, id); |
523 | end_progress = keyframes->keyframe_progress[k]; |
524 | break; |
525 | } |
526 | } |
527 | |
528 | progress = (progress - start_progress) / (end_progress - start_progress); |
529 | |
530 | result = _gtk_css_value_transition (start: start_value, |
531 | end: end_value, |
532 | property_id: keyframes->property_ids[id], |
533 | progress); |
534 | |
535 | /* XXX: Dear spec, what's the correct thing to do here? */ |
536 | if (result == NULL) |
537 | return _gtk_css_value_ref (value: start_value); |
538 | |
539 | return result; |
540 | } |
541 | |
542 | |